SEBSERV-469 implementation

This commit is contained in:
anhefti 2024-02-22 09:01:15 +01:00
parent 074b63580b
commit 29265dd7e1
16 changed files with 200 additions and 87 deletions

View file

@ -11,6 +11,11 @@ package ch.ethz.seb.sebserver.gbl.model.sebconfig;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import java.util.Collection;
import java.util.Set;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.validator.constraints.URL; import org.hibernate.validator.constraints.URL;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
@ -52,6 +57,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
public static final String ATTR_ENCRYPT_CERTIFICATE_ASYM = "cert_encryption_asym"; public static final String ATTR_ENCRYPT_CERTIFICATE_ASYM = "cert_encryption_asym";
public static final String FILTER_ATTR_CREATION_DATE = "creation_date"; public static final String FILTER_ATTR_CREATION_DATE = "creation_date";
public static final String ATTR_EXAM_SELECTION = "exam_selection";
public enum ConfigPurpose { public enum ConfigPurpose {
START_EXAM, START_EXAM,
@ -174,6 +180,8 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_USER) @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_USER)
public final String lastUpdateUser; public final String lastUpdateUser;
@JsonProperty(SEBClientConfig.ATTR_EXAM_SELECTION)
public final Set<Long> selectedExams;
@JsonCreator @JsonCreator
public SEBClientConfig( public SEBClientConfig(
@ -204,7 +212,8 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
@JsonProperty(ATTR_ENCRYPT_CERTIFICATE_ASYM) final Boolean encryptCertificateAsym, @JsonProperty(ATTR_ENCRYPT_CERTIFICATE_ASYM) final Boolean encryptCertificateAsym,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) final Boolean active, @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) final Boolean active,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_TIME) final DateTime lastUpdateTime, @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_TIME) final DateTime lastUpdateTime,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_USER) final String lastUpdateUser) { @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_USER) final String lastUpdateUser,
@JsonProperty(SEBClientConfig.ATTR_EXAM_SELECTION) final Set<Long> selectedExams) {
this.id = id; this.id = id;
this.institutionId = institutionId; this.institutionId = institutionId;
@ -240,6 +249,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
this.active = active; this.active = active;
this.lastUpdateTime = lastUpdateTime; this.lastUpdateTime = lastUpdateTime;
this.lastUpdateUser = lastUpdateUser; this.lastUpdateUser = lastUpdateUser;
this.selectedExams = Utils.immutableSetOf(selectedExams);
} }
public SEBClientConfig(final Long institutionId, final POSTMapper postParams) { public SEBClientConfig(final Long institutionId, final POSTMapper postParams) {
@ -281,6 +291,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
this.active = false; this.active = false;
this.lastUpdateTime = postParams.getDateTime(CONFIGURATION_NODE.ATTR_LAST_UPDATE_TIME); this.lastUpdateTime = postParams.getDateTime(CONFIGURATION_NODE.ATTR_LAST_UPDATE_TIME);
this.lastUpdateUser = postParams.getString(CONFIGURATION_NODE.ATTR_LAST_UPDATE_USER); this.lastUpdateUser = postParams.getString(CONFIGURATION_NODE.ATTR_LAST_UPDATE_USER);
this.selectedExams = Utils.immutableSetOf(Utils.getIdsFromString(postParams.getString(SEBClientConfig.ATTR_EXAM_SELECTION)));
} }
@Override @Override
@ -424,6 +435,10 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
return this.lastUpdateUser; return this.lastUpdateUser;
} }
public Set<Long> getSelectedExams() {
return selectedExams;
}
@Override @Override
public String toString() { public String toString() {
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
@ -503,7 +518,8 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
this.encryptCertificateAsym, this.encryptCertificateAsym,
this.active, this.active,
this.lastUpdateTime, this.lastUpdateTime,
this.lastUpdateUser); this.lastUpdateUser,
this.selectedExams);
} }
public static SEBClientConfig createNew(final Long institutionId, final long pingIterval) { public static SEBClientConfig createNew(final Long institutionId, final long pingIterval) {
@ -533,6 +549,7 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
false, false,
false, false,
null, null,
null,
null); null);
} }

View file

@ -935,4 +935,22 @@ public final class Utils {
return "Basic " + base64Creds; return "Basic " + base64Creds;
} }
public static Set<Long> getIdsFromString(final String idsString) {
if (StringUtils.isBlank(idsString)) {
return Collections.emptySet();
}
return Arrays
.stream(StringUtils.split(idsString, Constants.LIST_SEPARATOR_CHAR))
.map(s -> {
try {
return Long.valueOf(s);
} catch (final Exception e) {
log.warn("Failed to parse String: {} to Long", s);
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
} }

View file

@ -134,6 +134,9 @@ public class SEBClientConfigForm implements TemplateComposer {
private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY = private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.encryptSecret.confirm"); new LocTextKey("sebserver.clientconfig.form.encryptSecret.confirm");
private static final LocTextKey FORM_EXAM_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.examselection");
private static final LocTextKey DELETE_CONFIRM = private static final LocTextKey DELETE_CONFIRM =
new LocTextKey("sebserver.clientconfig.action.delete.confirm"); new LocTextKey("sebserver.clientconfig.action.delete.confirm");
private static final LocTextKey DELETE_SUCCESS = private static final LocTextKey DELETE_SUCCESS =
@ -409,20 +412,28 @@ public class SEBClientConfigForm implements TemplateComposer {
.mandatory(!isReadonly)) .mandatory(!isReadonly))
.withDefaultSpanEmptyCell(3) .withDefaultSpanEmptyCell(3)
.addField(FormBuilder.multiComboSelection(
SEBClientConfig.ATTR_EXAM_SELECTION,
FORM_EXAM_SELECTION_TEXT_KEY,
StringUtils.join(clientConfig.selectedExams, Constants.LIST_SEPARATOR),
() -> pageService.getResourceService().getExamLogSelectionResources())
.withInputSpan(5))
.withDefaultSpanEmptyCell(1);
// VDI // VDI
.withDefaultSpanInput(2) // .withDefaultSpanInput(2)
.addFieldIf( // .addFieldIf(
() -> false, // TODO skipped for version 1.2 --> 1.3 or 1.4 // () -> false, // TODO skipped for version 1.2 --> 1.3 or 1.4
() -> FormBuilder.singleSelection( // () -> FormBuilder.singleSelection(
SEBClientConfig.ATTR_VDI_TYPE, // SEBClientConfig.ATTR_VDI_TYPE,
VDI_TYPE_TEXT_KEY, // VDI_TYPE_TEXT_KEY,
clientConfig.vdiType != null // clientConfig.vdiType != null
? clientConfig.vdiType.name() // ? clientConfig.vdiType.name()
: SEBClientConfig.VDIType.NO.name(), // : SEBClientConfig.VDIType.NO.name(),
() -> this.pageService.getResourceService().vdiTypeResources()) // () -> this.pageService.getResourceService().vdiTypeResources())
.mandatory(!isReadonly)) // .mandatory(!isReadonly))
.withDefaultSpanEmptyCell(3); // .withDefaultSpanEmptyCell(3);
// VDI Attributes // VDI Attributes
@ -460,7 +471,10 @@ public class SEBClientConfigForm implements TemplateComposer {
FALLBACK_TEXT_KEY, FALLBACK_TEXT_KEY,
clientConfig.fallback != null clientConfig.fallback != null
? clientConfig.fallback.toString() ? clientConfig.fallback.toString()
: Constants.FALSE_STRING)); : Constants.FALSE_STRING))
.withDefaultSpanEmptyCell(3)
;
// Fallback Attributes // Fallback Attributes

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gui.form;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -122,6 +123,7 @@ public final class SelectionFieldBuilder extends FieldBuilder<String> {
this.itemsSupplier.get() this.itemsSupplier.get()
.stream() .stream()
.filter(tuple -> keys.contains(tuple._1)) .filter(tuple -> keys.contains(tuple._1))
.sorted((t1, t2) -> String.CASE_INSENSITIVE_ORDER.compare(t1._2, t2._2))
.map(tuple -> tuple._1) .map(tuple -> tuple._1)
.forEach(v -> buildReadonlyLabel(builder, composite, v, 1)); .forEach(v -> buildReadonlyLabel(builder, composite, v, 1));
} }

View file

@ -8,22 +8,17 @@
package ch.ethz.seb.sebserver.gui.widget; package ch.ethz.seb.sebserver.gui.widget;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.widgets.DropDown; import org.eclipse.rap.rwt.widgets.DropDown;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.*;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -33,13 +28,11 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService;
public final class MultiSelectionCombo extends Composite implements Selection { public final class MultiSelectionCombo extends Composite implements Selection {
public static final LocTextKey DESELECT_TOOLTIP = new LocTextKey( "sebserver.form.multiselect.deselect.tooltip" );
private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class); private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class);
private static final long serialVersionUID = -7787134114963647332L; private static final long serialVersionUID = -7787134114963647332L;
private final WidgetFactory widgetFactory;
private final List<Control> selectionControls = new ArrayList<>();
private final List<Tuple<String>> valueMapping = new ArrayList<>(); private final List<Tuple<String>> valueMapping = new ArrayList<>();
private final List<Tuple<String>> availableValues = new ArrayList<>(); private final List<Tuple<String>> availableValues = new ArrayList<>();
private final List<Tuple<String>> selectedValues = new ArrayList<>(); private final List<Tuple<String>> selectedValues = new ArrayList<>();
@ -50,7 +43,10 @@ public final class MultiSelectionCombo extends Composite implements Selection {
private final Composite updateAnchor; private final Composite updateAnchor;
private final String testKey; private final String testKey;
private final Table selectionTable;
private Listener listener = null; private Listener listener = null;
private WidgetFactory widgetFactory;
MultiSelectionCombo( MultiSelectionCombo(
final Composite parent, final Composite parent,
@ -78,7 +74,7 @@ public final class MultiSelectionCombo extends Composite implements Selection {
this.textInput.addListener(SWT.FocusIn, event -> openDropDown()); this.textInput.addListener(SWT.FocusIn, event -> openDropDown());
this.textInput.addListener(SWT.Modify, event -> openDropDown()); this.textInput.addListener(SWT.Modify, event -> openDropDown());
this.textInput.addListener(SWT.MouseUp, event -> openDropDown()); this.textInput.addListener(SWT.MouseUp, event -> openDropDown());
this.dropDown.addListener(SWT.Selection, event -> { this.dropDown.addListener(SWT.DefaultSelection, event -> {
final int selectionIndex = this.dropDown.getSelectionIndex(); final int selectionIndex = this.dropDown.getSelectionIndex();
if (selectionIndex >= 0) { if (selectionIndex >= 0) {
final String selectedItem = this.dropDown.getItems()[selectionIndex]; final String selectedItem = this.dropDown.getItems()[selectionIndex];
@ -86,21 +82,29 @@ public final class MultiSelectionCombo extends Composite implements Selection {
} }
}); });
selectionTable = widgetFactory.tableLocalized(this, SWT.NONE);
final GridLayout tableLayout = new GridLayout(1, true);
selectionTable.setLayout(tableLayout);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
selectionTable.setLayoutData(gridData);
//selectionTable.setToolTipText();
selectionTable.addListener(SWT.MouseDoubleClick, this::removeComboSelection);
selectionTable.addListener(SWT.Selection, event -> {
selectionTable.setSelection(-1);
});
selectionTable.setHeaderVisible(false);
selectionTable.setLinesVisible(true);
this.updateAnchor = updateAnchor; this.updateAnchor = updateAnchor;
} }
private void openDropDown() { @Override
final String text = this.textInput.getText(); public void setToolTipText(final String tooltipText) {
if (text == null) { if (tooltipText == null) {
this.dropDown.setVisible(false); super.setToolTipText(widgetFactory.getI18nSupport().getText(DESELECT_TOOLTIP));
return; return;
} }
this.dropDown.setItems(this.availableValues super.setToolTipText(tooltipText + "\n\n" + widgetFactory.getI18nSupport().getText(DESELECT_TOOLTIP));
.stream()
.filter(it -> it._2 != null && it._2.startsWith(text))
.map(t -> t._2).toArray(String[]::new));
this.dropDown.setSelectionIndex(0);
this.dropDown.setVisible(true);
} }
@Override @Override
@ -157,9 +161,6 @@ public final class MultiSelectionCombo extends Composite implements Selection {
@Override @Override
public void clear() { public void clear() {
this.selectedValues.clear(); this.selectedValues.clear();
this.selectionControls
.forEach(Control::dispose);
this.selectionControls.clear();
this.availableValues.clear(); this.availableValues.clear();
this.availableValues.addAll(this.valueMapping); this.availableValues.addAll(this.valueMapping);
} }
@ -170,42 +171,39 @@ public final class MultiSelectionCombo extends Composite implements Selection {
} }
this.selectedValues.add(item); this.selectedValues.add(item);
final Label label = this.widgetFactory.label(this, item._2);
label.setData(OPTION_VALUE, item._2);
final GridData textCell = new GridData(SWT.LEFT, SWT.CENTER, true, true);
label.setLayoutData(textCell);
label.addListener(SWT.MouseDoubleClick, this::removeComboSelection);
this.selectionControls.add(label);
WidgetFactory.setARIALabel(label, item._2); sortSelectedTable();
WidgetFactory.setTestId(label, (this.testKey != null) ? this.testKey + "_" + item._1 : item._1);
this.availableValues.remove(item); this.availableValues.remove(item);
PageService.updateScrolledComposite(this); PageService.updateScrolledComposite(this);
this.updateAnchor.layout(true, true); this.updateAnchor.layout(true, true);
} }
private void sortSelectedTable() {
selectionTable.removeAll();
selectedValues.sort((t1, t2) -> String.CASE_INSENSITIVE_ORDER.compare(t1._2, t2._2));
selectedValues.stream().forEach(t -> {
final TableItem tItem = new TableItem(selectionTable, SWT.NONE);
tItem.setText(0, t._2);
tItem.setData("tuple", t);
WidgetFactory.setARIALabel(tItem, t._2);
WidgetFactory.setTestId(tItem, (this.testKey != null) ? this.testKey + "_" + t._1 : t._1);
});
}
private void removeComboSelection(final Event event) { private void removeComboSelection(final Event event) {
if (event.widget == null) { if (event.widget == null) {
return; return;
} }
final String selectionKey = (String) event.widget.getData(OPTION_VALUE); final TableItem item = selectionTable.getItem(new Point(event.x, event.y));
final Optional<Control> findFirst = this.selectionControls.stream() @SuppressWarnings("unchecked")
.filter(t -> selectionKey.equals(t.getData(OPTION_VALUE))) final Tuple<String> value = (Tuple<String>) item.getData("tuple");
.findFirst(); this.selectedValues.remove(value);
if (!findFirst.isPresent()) {
return;
}
final Control control = findFirst.get();
final int indexOf = this.selectionControls.indexOf(control);
this.selectionControls.remove(control);
control.dispose();
final Tuple<String> value = this.selectedValues.remove(indexOf);
this.availableValues.add(value); this.availableValues.add(value);
sortSelectedTable();
PageService.updateScrolledComposite(this); PageService.updateScrolledComposite(this);
this.updateAnchor.layout(true, true); this.updateAnchor.layout(true, true);
if (this.listener != null) { if (this.listener != null) {
@ -238,4 +236,18 @@ public final class MultiSelectionCombo extends Composite implements Selection {
return findFirst.orElse(null); return findFirst.orElse(null);
} }
private void openDropDown() {
final String text = this.textInput.getText();
if (text == null) {
this.dropDown.setVisible(false);
return;
}
this.dropDown.setItems(this.availableValues
.stream()
.filter(it -> it._2 != null && it._2.startsWith(text))
.map(t -> t._2).toArray(String[]::new));
this.dropDown.setSelectionIndex(0);
this.dropDown.setVisible(true);
}
} }

View file

@ -489,7 +489,11 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
additionalAttributes.containsKey(SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ASYM), additionalAttributes.containsKey(SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ASYM),
BooleanUtils.toBooleanObject(record.getActive()), BooleanUtils.toBooleanObject(record.getActive()),
Utils.toDateTimeUTC(record.getLastUpdateTime()), Utils.toDateTimeUTC(record.getLastUpdateTime()),
record.getLastUpdateUser())); record.getLastUpdateUser(),
Utils.getIdsFromString(
additionalAttributes.containsKey(SEBClientConfig.ATTR_EXAM_SELECTION)
? additionalAttributes.get(SEBClientConfig.ATTR_EXAM_SELECTION).getValue()
: null)));
} }
private String getEncryptionPassword(final SEBClientConfig sebClientConfig) { private String getEncryptionPassword(final SEBClientConfig sebClientConfig) {
@ -685,6 +689,21 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
configId, configId,
SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ASYM); SEBClientConfig.ATTR_ENCRYPT_CERTIFICATE_ASYM);
} }
final Set<Long> selectedExams = sebClientConfig.getSelectedExams();
if (selectedExams != null && !selectedExams.isEmpty()) {
final String ids = StringUtils.join(selectedExams, Constants.LIST_SEPARATOR);
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.SEB_CLIENT_CONFIGURATION,
configId,
SEBClientConfig.ATTR_EXAM_SELECTION,
ids);
} else {
this.additionalAttributesDAO.delete(
EntityType.SEB_CLIENT_CONFIGURATION,
configId,
SEBClientConfig.ATTR_EXAM_SELECTION);
}
} }
private Long disposeSEBClientConfig(final Long pk) { private Long disposeSEBClientConfig(final Long pk) {

View file

@ -63,5 +63,5 @@ public interface ClientConfigService {
boolean checkAccess(SEBClientConfig config); boolean checkAccess(SEBClientConfig config);
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME) @Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
void initalCheckAccess(SEBClientConfig config); void initialCheckAccess(SEBClientConfig config);
} }

View file

@ -504,7 +504,7 @@ public class ClientConfigServiceImpl implements ClientConfigService {
} }
@Override @Override
public void initalCheckAccess(final SEBClientConfig config) { public void initialCheckAccess(final SEBClientConfig config) {
checkAccess(config); checkAccess(config);
} }

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.session; package ch.ethz.seb.sebserver.webservice.servicelayer.session;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.Principal;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -130,9 +131,10 @@ public interface ExamSessionService {
/** Gets all currently running Exams for a particular Institution. /** Gets all currently running Exams for a particular Institution.
* *
* @param institutionId the Institution identifier * @param institutionId the Institution identifier
* @param examSelectionFilter Exam selection filter from SEB connection configuration
* @return Result referencing the list of all currently running Exams of the institution or to an error if * @return Result referencing the list of all currently running Exams of the institution or to an error if
* happened. */ * happened. */
Result<Collection<Exam>> getRunningExamsForInstitution(Long institutionId); Result<Collection<Exam>> getRunningExams(Long institutionId, Predicate<Long> examSelectionFilter);
/** Gets all currently running Exams for a particular FilterMap. /** Gets all currently running Exams for a particular FilterMap.
* *

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.Principal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -251,9 +252,10 @@ public class ExamSessionServiceImpl implements ExamSessionService {
} }
@Override @Override
public Result<Collection<Exam>> getRunningExamsForInstitution(final Long institutionId) { public Result<Collection<Exam>> getRunningExams(final Long institutionId, final Predicate<Long> examSelectionFilter) {
return this.examDAO.allIdsOfRunning(institutionId) return this.examDAO.allIdsOfRunning(institutionId)
.map(col -> col.stream() .map(col -> col.stream()
.filter(examSelectionFilter)
.map(this::getRunningExam) .map(this::getRunningExam)
.filter(Result::hasValue) .filter(Result::hasValue)
.map(Result::get) .map(Result::get)

View file

@ -11,17 +11,17 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.Principal; import java.security.Principal;
import java.util.Arrays; import java.util.*;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -132,7 +132,10 @@ public class ExamAPI_V1_Controller {
// Crate list of running exams // Crate list of running exams
final List<RunningExamInfo> result; final List<RunningExamInfo> result;
if (examId == null) { if (examId == null) {
result = this.examSessionService.getRunningExamsForInstitution(institutionId)
result = this.examSessionService.getRunningExams(
institutionId,
getExamSelectionPredicate(principal.getName()))
.getOrThrow() .getOrThrow()
.stream() .stream()
.map(this::createRunningExamInfo) .map(this::createRunningExamInfo)
@ -161,17 +164,7 @@ public class ExamAPI_V1_Controller {
this.executor); this.executor);
} }
private boolean checkConsistency(final RunningExamInfo info) {
if (StringUtils.isNotBlank(info.name) &&
StringUtils.isNotBlank(info.url) &&
StringUtils.isNotBlank(info.examId)) {
return true;
}
log.warn("Invalid running exam detected. Filter out exam : {}", info);
return false;
}
@RequestMapping( @RequestMapping(
path = API.EXAM_API_HANDSHAKE_ENDPOINT, path = API.EXAM_API_HANDSHAKE_ENDPOINT,
@ -420,4 +413,31 @@ public class ExamAPI_V1_Controller {
.onSuccess(bek -> response.setHeader(API.EXAM_API_EXAM_ALT_BEK, bek)); .onSuccess(bek -> response.setHeader(API.EXAM_API_EXAM_ALT_BEK, bek));
} }
private Predicate<Long> getExamSelectionPredicate(final String clientName) {
return this.sebClientConfigDAO
.byClientName(clientName)
.map(this::getExamSelectionPredicate)
.onError(error -> log.warn("Failed to get SEB connection configuration by name: {}", clientName))
.getOr(Utils.truePredicate());
}
private Predicate<Long> getExamSelectionPredicate(final SEBClientConfig config) {
if (config == null || config.selectedExams.isEmpty()) {
return Utils.truePredicate();
}
return config.getSelectedExams()::contains;
}
private boolean checkConsistency(final RunningExamInfo info) {
if (StringUtils.isNotBlank(info.name) &&
StringUtils.isNotBlank(info.url) &&
StringUtils.isNotBlank(info.examId)) {
return true;
}
log.warn("Invalid running exam detected. Filter out exam : {}", info);
return false;
}
} }

View file

@ -182,7 +182,7 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
protected Result<SEBClientConfig> notifySaved(final SEBClientConfig entity) { protected Result<SEBClientConfig> notifySaved(final SEBClientConfig entity) {
if (entity.isActive()) { if (entity.isActive()) {
// try to get access token for SEB client // try to get access token for SEB client
this.sebClientConfigService.initalCheckAccess(entity); this.sebClientConfigService.initialCheckAccess(entity);
} }
return super.notifySaved(entity); return super.notifySaved(entity);
} }

View file

@ -129,6 +129,7 @@ sebserver.form.mandatory.label={0} mandatory
sebserver.form.confirm.label=confirm {0} sebserver.form.confirm.label=confirm {0}
sebserver.form.tablefilter.label={0} table-column filter sebserver.form.tablefilter.label={0} table-column filter
sebserver.table.column.sort.default.tooltip=Click on the column header to sort the table within this column sebserver.table.column.sort.default.tooltip=Click on the column header to sort the table within this column
sebserver.form.multiselect.deselect.tooltip=Please use double-click to deselect selected items.
sebserver.dialog.confirm.deactivation=Note that there are {0} other entities that belong to this entity.<br/>Those will also be deactivated by deactivating this entity.<br/><br/>Are you sure to deactivate this entity? sebserver.dialog.confirm.deactivation=Note that there are {0} other entities that belong to this entity.<br/>Those will also be deactivated by deactivating this entity.<br/><br/>Are you sure to deactivate this entity?
sebserver.dialog.confirm.deactivation.noDependencies=Are you sure you want to deactivate? sebserver.dialog.confirm.deactivation.noDependencies=Are you sure you want to deactivate?
@ -1033,6 +1034,8 @@ sebserver.clientconfig.form.certificate=Encrypt with Certificate
sebserver.clientconfig.form.certificate.tooltip=Choose identity certificate to be used for encrypting the connection configuration sebserver.clientconfig.form.certificate.tooltip=Choose identity certificate to be used for encrypting the connection configuration
sebserver.clientconfig.form.type.async=Use asymmetric-only encryption sebserver.clientconfig.form.type.async=Use asymmetric-only encryption
sebserver.clientconfig.form.type.async.tooltip=Use old asymmetric-only encryption (for SEB < 2.2) sebserver.clientconfig.form.type.async.tooltip=Use old asymmetric-only encryption (for SEB < 2.2)
sebserver.clientconfig.form.examselection=Exams
sebserver.clientconfig.form.examselection.tooltip=List of Exams selected to work with this Connection Configuration.
sebserver.clientconfig.form.credentials.title=Client Credentials of Connection Configuration sebserver.clientconfig.form.credentials.title=Client Credentials of Connection Configuration
sebserver.clientconfig.form.credentials.info=A SEB client that loads this connection configuration<br/>uses the following credentials to securely connect to the SEB Server. sebserver.clientconfig.form.credentials.info=A SEB client that loads this connection configuration<br/>uses the following credentials to securely connect to the SEB Server.

View file

@ -138,7 +138,8 @@ public class ModelObjectJSONGenerator {
false, false,
true, true,
DateTime.now(), DateTime.now(),
"user123"); "user123",
null);
System.out.println(domainObject.getClass().getSimpleName() + ":"); System.out.println(domainObject.getClass().getSimpleName() + ":");
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject)); System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));

View file

@ -128,6 +128,7 @@ public class ClientConfigTest extends GuiIntegrationTest {
false, false,
null, null,
null, null,
null,
null)) null))
.call(); .call();
@ -161,6 +162,7 @@ public class ClientConfigTest extends GuiIntegrationTest {
false, false,
null, null,
null, null,
null,
null)) null))
.call() .call()
.getOrThrow(); .getOrThrow();

View file

@ -12,6 +12,7 @@ import static org.junit.Assert.*;
import java.util.Collection; import java.util.Collection;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Order;
@ -61,7 +62,7 @@ public class ExamProctoringRoomServiceTest extends AdministrationAPIIntegrationT
@Order(1) @Order(1)
public void test01_checkExamRunning() { public void test01_checkExamRunning() {
final Result<Collection<Exam>> runningExamsForInstitution = final Result<Collection<Exam>> runningExamsForInstitution =
this.examSessionService.getRunningExamsForInstitution(1L); this.examSessionService.getRunningExams(1L, Utils.truePredicate());
assertFalse(runningExamsForInstitution.hasError()); assertFalse(runningExamsForInstitution.hasError());
final Collection<Exam> collection = runningExamsForInstitution.get(); final Collection<Exam> collection = runningExamsForInstitution.get();
assertFalse(collection.isEmpty()); assertFalse(collection.isEmpty());