SEBSERV-335 implementation
This commit is contained in:
parent
4bcc6cc9cb
commit
9d80a94bbf
39 changed files with 1460 additions and 212 deletions
|
@ -106,6 +106,8 @@ public final class API {
|
|||
|
||||
public static final String EXAM_API_SEB_CONNECTION_TOKEN = "SEBConnectionToken";
|
||||
|
||||
public static final String EXAM_API_EXAM_SIGNATURE_SALT_HEADER = "SEBExamSalt";
|
||||
|
||||
public static final String EXAM_API_USER_SESSION_ID = "seb_user_session_id";
|
||||
|
||||
public static final String EXAM_API_HANDSHAKE_ENDPOINT = "/handshake";
|
||||
|
@ -146,7 +148,6 @@ public final class API {
|
|||
public static final String QUIZ_DISCOVERY_ENDPOINT = "/quiz";
|
||||
|
||||
public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam";
|
||||
//public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/download-config";
|
||||
public static final String EXAM_ADMINISTRATION_ARCHIVE_PATH_SEGMENT = "/archive";
|
||||
public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT = "/check-consistency";
|
||||
public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_INCLUDE_RESTRICTION = "include-restriction";
|
||||
|
@ -156,7 +157,7 @@ public final class API {
|
|||
public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_CHAPTERS_PATH_SEGMENT = "/chapters";
|
||||
public static final String EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT = "/proctoring";
|
||||
public static final String EXAM_ADMINISTRATION_SEB_SECURITY_KEY_GRANTS_PATH_SEGMENT = "/grant";
|
||||
public static final String EXAM_ADMINISTRATION_SEB_SECURITY_AS_KEYS_PATH_SEGMENT = "/signature-key";
|
||||
public static final String EXAM_ADMINISTRATION_SEB_SECURITY_KEY_INFO_PATH_SEGMENT = "/sebkeyinfo";
|
||||
|
||||
public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";
|
||||
public static final String EXAM_CLIENT_GROUP_ENDPOINT = "/client-group";
|
||||
|
|
|
@ -31,6 +31,8 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
|||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
|
||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
|
@ -62,6 +64,7 @@ public final class Exam implements GrantEntity {
|
|||
public static final String FILTER_CACHED_QUIZZES = "cached-quizzes";
|
||||
|
||||
public static final String ATTR_ADDITIONAL_ATTRIBUTES = "additionalAttributes";
|
||||
|
||||
/** This attribute name is used on exams to store the flag for indicating the signature key check */
|
||||
public static final String ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED = "SIGNATURE_KEY_CHECK_ENABLED";
|
||||
/** This attribute name is used to store the signature check grant threshold for statistical checks */
|
||||
|
@ -69,6 +72,8 @@ public final class Exam implements GrantEntity {
|
|||
/** This attribute name is used to store the signature check encryption certificate is one is used */
|
||||
public static final String ADDITIONAL_ATTR_SIGNATURE_KEY_CERT_ALIAS = "SIGNATURE_KEY_CERT_ALIAS";
|
||||
|
||||
public static final String ADDITIONAL_ATTR_SIGNATURE_KEY_SALT = "SIGNATURE_KEY_SALT";
|
||||
|
||||
public enum ExamStatus {
|
||||
UP_COMING,
|
||||
RUNNING,
|
||||
|
|
|
@ -10,21 +10,26 @@ package ch.ethz.seb.sebserver.gbl.model.institution;
|
|||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.apache.tomcat.util.buf.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain.SEB_SECURITY_KEY_REGISTRY;
|
||||
import ch.ethz.seb.sebserver.gbl.model.ModelIdAware;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class AppSignatureKeyInfo {
|
||||
public class AppSignatureKeyInfo implements ModelIdAware {
|
||||
|
||||
public static final String ATTR_KEY_CONNECTION_MAPPING = "kcMapping";
|
||||
public static final String ATTR_NUMBER_OF_CONNECTIONS = "numConnections";
|
||||
public static final String ATTR_CONNECTION_IDS = "connectionIds";
|
||||
|
||||
@NotNull
|
||||
@JsonProperty(SEB_SECURITY_KEY_REGISTRY.ATTR_INSTITUTION_ID)
|
||||
|
@ -34,18 +39,23 @@ public class AppSignatureKeyInfo {
|
|||
@JsonProperty(SEB_SECURITY_KEY_REGISTRY.ATTR_EXAM_ID)
|
||||
public final Long examId;
|
||||
|
||||
@JsonProperty(ATTR_KEY_CONNECTION_MAPPING)
|
||||
public final Map<String, Set<Long>> keyConnectionMapping;
|
||||
@JsonProperty(SEB_SECURITY_KEY_REGISTRY.ATTR_KEY_VALUE)
|
||||
public final String key;
|
||||
|
||||
@JsonProperty(ATTR_CONNECTION_IDS)
|
||||
public final Map<Long, String> connectionIds;
|
||||
|
||||
@JsonCreator
|
||||
public AppSignatureKeyInfo(
|
||||
@JsonProperty(SEB_SECURITY_KEY_REGISTRY.ATTR_INSTITUTION_ID) final Long institutionId,
|
||||
@JsonProperty(SEB_SECURITY_KEY_REGISTRY.ATTR_EXAM_ID) final Long examId,
|
||||
@JsonProperty(ATTR_KEY_CONNECTION_MAPPING) final Map<String, Set<Long>> keyConnectionMapping) {
|
||||
@JsonProperty(SEB_SECURITY_KEY_REGISTRY.ATTR_KEY_VALUE) final String key,
|
||||
@JsonProperty(ATTR_CONNECTION_IDS) final Map<Long, String> connectionIds) {
|
||||
|
||||
this.institutionId = institutionId;
|
||||
this.examId = examId;
|
||||
this.keyConnectionMapping = Utils.immutableMapOf(keyConnectionMapping);
|
||||
this.key = key;
|
||||
this.connectionIds = Utils.immutableMapOf(connectionIds);
|
||||
}
|
||||
|
||||
public Long getInstitutionId() {
|
||||
|
@ -56,8 +66,27 @@ public class AppSignatureKeyInfo {
|
|||
return this.examId;
|
||||
}
|
||||
|
||||
public Map<String, Set<Long>> getKeyConnectionMapping() {
|
||||
return this.keyConnectionMapping;
|
||||
@Override
|
||||
public String getModelId() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return this.key;
|
||||
}
|
||||
|
||||
public Map<Long, String> getConnectionIds() {
|
||||
return this.connectionIds;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getConnectionNames() {
|
||||
return StringUtils.join(this.connectionIds.values(), Constants.LIST_SEPARATOR_CHAR);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public int getNumberOfConnections() {
|
||||
return this.connectionIds.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -84,8 +113,10 @@ public class AppSignatureKeyInfo {
|
|||
builder.append(this.institutionId);
|
||||
builder.append(", examId=");
|
||||
builder.append(this.examId);
|
||||
builder.append(", keyConnectionMapping=");
|
||||
builder.append(this.keyConnectionMapping);
|
||||
builder.append(", key=");
|
||||
builder.append(this.key);
|
||||
builder.append(", connectionIds=");
|
||||
builder.append(this.connectionIds);
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
|
|
@ -29,6 +29,9 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
|
|||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public final class ClientConnection implements GrantEntity {
|
||||
|
||||
/** This attribute name is used to store the App-Signature-Key given by a SEB Client */
|
||||
public static final String ADDITIONAL_ATTR_APP_SIGNATURE_KEY = "APP_SIGNATURE_KEY";
|
||||
|
||||
public enum ConnectionStatus {
|
||||
UNDEFINED(0, false, false),
|
||||
CONNECTION_REQUESTED(1, true, false),
|
||||
|
@ -55,6 +58,11 @@ public final class ClientConnection implements GrantEntity {
|
|||
ConnectionStatus.AUTHENTICATED.name(),
|
||||
ConnectionStatus.CONNECTION_REQUESTED.name());
|
||||
|
||||
public final static List<String> SECURE_STATES = Utils.immutableListOf(
|
||||
ConnectionStatus.ACTIVE.name(),
|
||||
ConnectionStatus.AUTHENTICATED.name(),
|
||||
ConnectionStatus.CLOSED.name());
|
||||
|
||||
public static final ClientConnection EMPTY_CLIENT_CONNECTION = new ClientConnection(
|
||||
-1L, -1L, -1L,
|
||||
ConnectionStatus.UNDEFINED,
|
||||
|
|
|
@ -25,6 +25,7 @@ public class ClientMonitoringData implements ClientMonitoringDataView {
|
|||
public final ConnectionStatus status;
|
||||
public final Map<Long, String> indicatorVals;
|
||||
public final boolean missingPing;
|
||||
public final boolean missingGrant;
|
||||
public final boolean pendingNotification;
|
||||
|
||||
@JsonCreator
|
||||
|
@ -33,12 +34,14 @@ public class ClientMonitoringData implements ClientMonitoringDataView {
|
|||
@JsonProperty(ATTR_STATUS) final ConnectionStatus status,
|
||||
@JsonProperty(ATTR_INDICATOR_VALUES) final Map<Long, String> indicatorVals,
|
||||
@JsonProperty(ATTR_MISSING_PING) final boolean missingPing,
|
||||
@JsonProperty(ATTR_MISSING_GRANT) final boolean missingGrant,
|
||||
@JsonProperty(ATTR_PENDING_NOTIFICATION) final boolean pendingNotification) {
|
||||
|
||||
this.id = id;
|
||||
this.status = status;
|
||||
this.indicatorVals = indicatorVals;
|
||||
this.missingPing = missingPing;
|
||||
this.missingGrant = missingGrant;
|
||||
this.pendingNotification = pendingNotification;
|
||||
}
|
||||
|
||||
|
@ -62,6 +65,12 @@ public class ClientMonitoringData implements ClientMonitoringDataView {
|
|||
return this.missingPing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMissingGrant() {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPendingNotification() {
|
||||
return this.pendingNotification;
|
||||
|
|
|
@ -27,6 +27,7 @@ public interface ClientMonitoringDataView {
|
|||
public static final String ATTR_INDICATOR_VALUES = "iv";
|
||||
public static final String ATTR_CLIENT_GROUPS = "cg";
|
||||
public static final String ATTR_MISSING_PING = "mp";
|
||||
public static final String ATTR_MISSING_GRANT = "mg";
|
||||
public static final String ATTR_PENDING_NOTIFICATION = "pn";
|
||||
|
||||
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID)
|
||||
|
@ -41,6 +42,9 @@ public interface ClientMonitoringDataView {
|
|||
@JsonProperty(ATTR_MISSING_PING)
|
||||
boolean isMissingPing();
|
||||
|
||||
@JsonProperty(ATTR_MISSING_GRANT)
|
||||
boolean isMissingGrant();
|
||||
|
||||
@JsonProperty(ATTR_PENDING_NOTIFICATION)
|
||||
boolean isPendingNotification();
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
@ -69,6 +70,19 @@ public final class Utils {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(Utils.class);
|
||||
|
||||
/** Use this it merge two maps into a new one. Let the given maps unmodified.
|
||||
*
|
||||
* @param <K> Key type
|
||||
* @param <T> Value type
|
||||
* @param m1 First Map
|
||||
* @param m2 Second Map
|
||||
* @return new Map with merged entries from m1 and m2 */
|
||||
public static <K, T> Map<K, T> mergeMap(final Map<K, T> m1, final Map<K, T> m2) {
|
||||
final HashMap<K, T> hashMap = new HashMap<>(m1);
|
||||
hashMap.putAll(m2);
|
||||
return hashMap;
|
||||
}
|
||||
|
||||
/** This Collector can be used within stream collect to get one expected singleton element from
|
||||
* the given Stream.
|
||||
* This first collects the given Stream to a list and then check if there is one expected element.
|
||||
|
|
|
@ -21,6 +21,8 @@ public enum ActionCategory {
|
|||
EXAM_TEMPLATE_LIST(new LocTextKey("sebserver.examtemplate.list.actions"), 1),
|
||||
INDICATOR_TEMPLATE_LIST(new LocTextKey("sebserver.examtemplate.indicator.list.actions"), 1),
|
||||
CLIENT_GROUP_TEMPLATE_LIST(new LocTextKey("sebserver.examtemplate.clientgroup.list.actions"), 2),
|
||||
APP_SIGNATURE_KEY_LIST(new LocTextKey("sebserver.exam.signaturekey.keylist.actions"), 1),
|
||||
SECURITY_KEY_GRANT_LIST(new LocTextKey("sebserver.exam.signaturekey.grantlist.actions"), 2),
|
||||
EXAM_CONFIG_MAPPING_LIST(new LocTextKey("sebserver.exam.configuration.list.actions"), 1),
|
||||
INDICATOR_LIST(new LocTextKey("sebserver.exam.indicator.list.actions"), 2),
|
||||
CLIENT_GROUP_LIST(new LocTextKey("sebserver.exam.clientgroup.list.actions"), 3),
|
||||
|
|
|
@ -426,6 +426,32 @@ public enum ActionDefinition {
|
|||
PageStateDefinitionImpl.SECURITY_KEY_EDIT,
|
||||
ActionCategory.FORM),
|
||||
|
||||
EXAM_SECURITY_KEY_SAVE_SETTINGS(
|
||||
new LocTextKey("sebserver.exam.signaturekey.action.save"),
|
||||
ImageIcon.SAVE,
|
||||
PageStateDefinitionImpl.EXAM_VIEW,
|
||||
ActionCategory.FORM),
|
||||
EXAM_SECURITY_KEY_CANCEL_MODIFY(
|
||||
new LocTextKey("sebserver.exam.signaturekey.action.cancel"),
|
||||
ImageIcon.CANCEL,
|
||||
PageStateDefinitionImpl.EXAM_VIEW,
|
||||
ActionCategory.FORM),
|
||||
EXAM_SECURITY_KEY_SHOW_ADD_GRANT_POPUP(
|
||||
new LocTextKey("sebserver.exam.signaturekey.action.addGrant"),
|
||||
ImageIcon.ADD,
|
||||
PageStateDefinitionImpl.SECURITY_KEY_EDIT,
|
||||
ActionCategory.APP_SIGNATURE_KEY_LIST),
|
||||
EXAM_SECURITY_KEY_SHOW_GRANT_POPUP(
|
||||
new LocTextKey("sebserver.exam.signaturekey.action.showGrant"),
|
||||
ImageIcon.SHOW,
|
||||
PageStateDefinitionImpl.SECURITY_KEY_EDIT,
|
||||
ActionCategory.SECURITY_KEY_GRANT_LIST),
|
||||
EXAM_SECURITY_KEY_DELETE_GRANT(
|
||||
new LocTextKey("sebserver.exam.signaturekey.action.deleteGrant"),
|
||||
ImageIcon.DELETE,
|
||||
PageStateDefinitionImpl.SECURITY_KEY_EDIT,
|
||||
ActionCategory.SECURITY_KEY_GRANT_LIST),
|
||||
|
||||
EXAM_SEB_CLIENT_CONFIG_EXPORT(
|
||||
new LocTextKey("sebserver.exam.action.createClientToStartExam"),
|
||||
ImageIcon.EXPORT,
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* 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.gui.content.exam;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.AppSignatureKeyInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetClientConnections;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GrantClientConnectionSecurityKey;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class AddSecurityKeyGrantPopup {
|
||||
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.signaturekey.seb.add.title");
|
||||
private static final LocTextKey TITLE_TEXT_INFO =
|
||||
new LocTextKey("sebserver.exam.signaturekey.seb.add.info");
|
||||
|
||||
private static final LocTextKey TITLE_TEXT_FORM_SIGNATURE =
|
||||
new LocTextKey("sebserver.exam.signaturekey.seb.add.signature");
|
||||
private static final LocTextKey TITLE_TEXT_FORM_TAG =
|
||||
new LocTextKey("sebserver.exam.signaturekey.seb.add.tag");
|
||||
|
||||
private static final LocTextKey TABLE_COLUMN_NAME =
|
||||
new LocTextKey("sebserver.exam.signaturekey.list.name");
|
||||
private static final LocTextKey TABLE_COLUMN_INFO =
|
||||
new LocTextKey("sebserver.exam.signaturekey.list.info");
|
||||
private static final LocTextKey TABLE_COLUMN_STATUS =
|
||||
new LocTextKey("sebserver.exam.signaturekey.list.status");
|
||||
|
||||
private final PageService pageService;
|
||||
|
||||
protected AddSecurityKeyGrantPopup(final PageService pageService) {
|
||||
this.pageService = pageService;
|
||||
}
|
||||
|
||||
public PageAction showGrantPopup(final PageAction action, final AppSignatureKeyInfo appSignatureKeyInfo) {
|
||||
final PageContext pageContext = action.pageContext();
|
||||
final PopupComposer popupComposer = new PopupComposer(this.pageService, pageContext, appSignatureKeyInfo);
|
||||
try {
|
||||
final ModalInputDialog<FormHandle<?>> dialog =
|
||||
new ModalInputDialog<>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
this.pageService.getWidgetFactory());
|
||||
dialog.setDialogWidth(800);
|
||||
|
||||
final Predicate<FormHandle<?>> applyGrant = formHandle -> applyGrant(
|
||||
pageContext,
|
||||
formHandle,
|
||||
appSignatureKeyInfo);
|
||||
|
||||
dialog.open(
|
||||
TITLE_TEXT_KEY,
|
||||
applyGrant,
|
||||
Utils.EMPTY_EXECUTION,
|
||||
popupComposer);
|
||||
|
||||
} catch (final Exception e) {
|
||||
action.pageContext().notifyUnexpectedError(e);
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
private final class PopupComposer implements ModalInputDialogComposer<FormHandle<?>> {
|
||||
|
||||
private final PageService pageService;
|
||||
private final PageContext pageContext;
|
||||
private final AppSignatureKeyInfo appSignatureKeyInfo;
|
||||
|
||||
protected PopupComposer(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final AppSignatureKeyInfo appSignatureKeyInfo) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.pageContext = pageContext;
|
||||
this.appSignatureKeyInfo = appSignatureKeyInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<FormHandle<?>> compose(final Composite parent) {
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
widgetFactory.addFormSubContextHeader(parent, TITLE_TEXT_INFO, null);
|
||||
|
||||
final PageContext formContext = this.pageContext.copyOf(parent);
|
||||
final FormHandle<?> form = this.pageService.formBuilder(formContext)
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_KEY_VALUE,
|
||||
TITLE_TEXT_FORM_SIGNATURE,
|
||||
String.valueOf(this.appSignatureKeyInfo.key))
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG,
|
||||
TITLE_TEXT_FORM_TAG)
|
||||
.mandatory())
|
||||
|
||||
.build();
|
||||
|
||||
final String clientConnectionIds = StringUtils.join(
|
||||
this.appSignatureKeyInfo.connectionIds
|
||||
.keySet()
|
||||
.stream()
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.toList()),
|
||||
Constants.LIST_SEPARATOR_CHAR);
|
||||
|
||||
this.pageService.getRestService().getBuilder(GetClientConnections.class)
|
||||
.withQueryParam(API.PARAM_MODEL_ID_LIST, clientConnectionIds)
|
||||
.call()
|
||||
.onSuccess(connections -> {
|
||||
final List<ClientConnection> list = new ArrayList<>();
|
||||
this.pageService.staticListTableBuilder(list, EntityType.CLIENT_CONNECTION)
|
||||
.withPaging(10)
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||
TABLE_COLUMN_NAME,
|
||||
ClientConnection::getUserSessionId)
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
ClientConnection.ATTR_INFO,
|
||||
TABLE_COLUMN_INFO,
|
||||
ClientConnection::getInfo)
|
||||
.widthProportion(3))
|
||||
|
||||
.withColumn(new ColumnDefinition<ClientConnection>(
|
||||
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
||||
TABLE_COLUMN_STATUS,
|
||||
row -> this.pageService.getResourceService()
|
||||
.localizedClientConnectionStatusName(row.getStatus()))
|
||||
.widthProportion(1));
|
||||
|
||||
});
|
||||
|
||||
return () -> form;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean applyGrant(
|
||||
final PageContext pageContext,
|
||||
final FormHandle<?> formHandle,
|
||||
final AppSignatureKeyInfo appSignatureKeyInfo) {
|
||||
|
||||
if (appSignatureKeyInfo.connectionIds.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final Long connectioId = appSignatureKeyInfo.connectionIds.keySet().iterator().next();
|
||||
|
||||
return this.pageService
|
||||
.getRestService()
|
||||
.getBuilder(GrantClientConnectionSecurityKey.class)
|
||||
.withURIVariable(API.PARAM_PARENT_MODEL_ID, String.valueOf(appSignatureKeyInfo.examId))
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(connectioId))
|
||||
.withFormBinding(formHandle.getFormBinding())
|
||||
.call()
|
||||
.onError(formHandle::handleError)
|
||||
.hasValue();
|
||||
}
|
||||
|
||||
}
|
|
@ -8,18 +8,41 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.content.exam;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.AppSignatureKeyInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.seckey.DeleteSecurityKeyGrant;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.seckey.GetAppSignatureKeyInfo;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.seckey.GetAppSignatureKeys;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.seckey.SaveAppSignatureKeySettings;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
|
@ -35,32 +58,51 @@ public class ExamSignatureKeyForm implements TemplateComposer {
|
|||
private static final LocTextKey FORM_STAT_GRANT_THRESHOLD =
|
||||
new LocTextKey("sebserver.exam.signaturekey.form.grant.threshold");
|
||||
|
||||
private static final LocTextKey GRANT_LIST_TITLE =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grantlist.title");
|
||||
private static final LocTextKey GRANT_LIST_KEY =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grantlist.key");
|
||||
private static final LocTextKey GRANT_LIST_TAG =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grantlist.tag");
|
||||
|
||||
private static final LocTextKey APP_SIG_KEY_EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.signaturekey.keylist.empty");
|
||||
private static final LocTextKey APP_SIG_KEY_LIST_TITLE =
|
||||
new LocTextKey("sebserver.exam.signaturekey.keylist.title");
|
||||
private static final LocTextKey APP_SIG_KEY_LIST_TITLE_TOOLTIP =
|
||||
new LocTextKey("sebserver.exam.signaturekey.keylist.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
||||
private static final LocTextKey APP_SIG_KEY_LIST_KEY =
|
||||
new LocTextKey("sebserver.exam.signaturekey.keylist.key");
|
||||
private static final LocTextKey APP_SIG_KEY_LIST_NUM_CLIENTS =
|
||||
new LocTextKey("sebserver.exam.signaturekey.keylist.clients");
|
||||
private static final LocTextKey APP_SIG_KEY_LIST_CLIENT_IDS =
|
||||
new LocTextKey("sebserver.exam.signaturekey.keylist.clientids");
|
||||
private static final LocTextKey APP_SIG_KEY_LIST_EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.signaturekey.keylist.pleaseSelect");
|
||||
|
||||
private static final LocTextKey GRANT_LIST_TITLE =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grantlist.title");
|
||||
private static final LocTextKey GRANT_LIST_TITLE_TOOLTIP =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grantlist.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
||||
private static final LocTextKey GRANT_LIST_EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grantlist..empty");
|
||||
private static final LocTextKey GRANT_LIST_KEY =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grantlist.key");
|
||||
private static final LocTextKey GRANT_LIST_TAG =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grantlist.tag");
|
||||
private static final LocTextKey GRANT_LIST_EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grantlist.pleaseSelect");
|
||||
private static final LocTextKey GRANT_LIST_DELETE_CONFORM =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grantlist.delete.confirm");
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final I18nSupport i18nSupport;
|
||||
private final AddSecurityKeyGrantPopup addSecurityKeyGrantPopup;
|
||||
private final SecurityKeyGrantPopup securityKeyGrantPopup;
|
||||
|
||||
public ExamSignatureKeyForm(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
final I18nSupport i18nSupport) {
|
||||
final AddSecurityKeyGrantPopup addSecurityKeyGrantPopup,
|
||||
final SecurityKeyGrantPopup securityKeyGrantPopup) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.i18nSupport = i18nSupport;
|
||||
this.addSecurityKeyGrantPopup = addSecurityKeyGrantPopup;
|
||||
this.securityKeyGrantPopup = securityKeyGrantPopup;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -68,9 +110,199 @@ public class ExamSignatureKeyForm implements TemplateComposer {
|
|||
final RestService restService = this.resourceService.getRestService();
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final Exam exam = restService
|
||||
.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
final boolean signatureKeyCheckEnabled = BooleanUtils.toBoolean(
|
||||
exam.additionalAttributes.get(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED));
|
||||
final String ct = exam.additionalAttributes.get(Exam.ADDITIONAL_ATTR_STATISTICAL_GRANT_COUNT_THRESHOLD);
|
||||
|
||||
// TODO Auto-generated method stub
|
||||
final Composite content = widgetFactory
|
||||
.defaultPageLayout(pageContext.getParent(), TILE);
|
||||
|
||||
final PageActionBuilder actionBuilder = this.pageService
|
||||
.pageActionBuilder(pageContext.clearEntityKeys());
|
||||
|
||||
final FormHandle<Entity> form = this.pageService
|
||||
.formBuilder(pageContext.copyOf(content))
|
||||
|
||||
.addField(FormBuilder.checkbox(
|
||||
Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED,
|
||||
FORM_ENABLED,
|
||||
String.valueOf(signatureKeyCheckEnabled)))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Exam.ADDITIONAL_ATTR_STATISTICAL_GRANT_COUNT_THRESHOLD,
|
||||
FORM_STAT_GRANT_THRESHOLD,
|
||||
(ct != null) ? ct : "2")
|
||||
.asNumber(number -> {
|
||||
if (StringUtils.isNotBlank(number)) {
|
||||
Integer.parseInt(number);
|
||||
}
|
||||
})
|
||||
.mandatory()
|
||||
.withInputSpan(1))
|
||||
|
||||
.build();
|
||||
|
||||
widgetFactory.addFormSubContextHeader(
|
||||
content,
|
||||
APP_SIG_KEY_LIST_TITLE,
|
||||
APP_SIG_KEY_LIST_TITLE_TOOLTIP);
|
||||
|
||||
final EntityTable<AppSignatureKeyInfo> connectionInfoTable = this.pageService
|
||||
.remoteListTableBuilder(
|
||||
restService.getRestCall(GetAppSignatureKeyInfo.class),
|
||||
EntityType.SEB_SECURITY_KEY_REGISTRY)
|
||||
.withRestCallAdapter(builder -> builder.withURIVariable(API.PARAM_PARENT_MODEL_ID, entityKey.modelId))
|
||||
.withEmptyMessage(APP_SIG_KEY_EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(-1)
|
||||
.hideNavigation()
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_KEY_VALUE,
|
||||
APP_SIG_KEY_LIST_KEY,
|
||||
AppSignatureKeyInfo::getKey)
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
AppSignatureKeyInfo.ATTR_NUMBER_OF_CONNECTIONS,
|
||||
APP_SIG_KEY_LIST_NUM_CLIENTS,
|
||||
AppSignatureKeyInfo::getNumberOfConnections)
|
||||
.widthProportion(1))
|
||||
|
||||
.withDefaultAction(table -> actionBuilder
|
||||
.newAction(ActionDefinition.EXAM_SECURITY_KEY_SHOW_ADD_GRANT_POPUP)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withExec(action -> this.addSecurityKeyGrantPopup.showGrantPopup(
|
||||
action,
|
||||
table.getSingleSelectedROWData()))
|
||||
.noEventPropagation()
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.create())
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.EXAM_SECURITY_KEY_SHOW_ADD_GRANT_POPUP))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
widgetFactory.addFormSubContextHeader(
|
||||
content,
|
||||
GRANT_LIST_TITLE,
|
||||
GRANT_LIST_TITLE_TOOLTIP);
|
||||
|
||||
final EntityTable<SecurityKey> grantsList = this.pageService
|
||||
.remoteListTableBuilder(
|
||||
restService.getRestCall(GetAppSignatureKeys.class),
|
||||
EntityType.SEB_SECURITY_KEY_REGISTRY)
|
||||
.withRestCallAdapter(builder -> builder.withURIVariable(API.PARAM_PARENT_MODEL_ID, entityKey.modelId))
|
||||
.withEmptyMessage(GRANT_LIST_EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(-1)
|
||||
.hideNavigation()
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_KEY_VALUE,
|
||||
GRANT_LIST_KEY,
|
||||
SecurityKey::getKey).widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG,
|
||||
GRANT_LIST_TAG,
|
||||
SecurityKey::getTag).widthProportion(1))
|
||||
|
||||
.withDefaultAction(table -> actionBuilder
|
||||
.newAction(ActionDefinition.EXAM_SECURITY_KEY_SHOW_GRANT_POPUP)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withExec(action -> this.securityKeyGrantPopup.showGrantPopup(
|
||||
action,
|
||||
table.getSingleSelectedROWData()))
|
||||
.noEventPropagation()
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.create())
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.EXAM_SECURITY_KEY_SHOW_GRANT_POPUP,
|
||||
ActionDefinition.EXAM_SECURITY_KEY_DELETE_GRANT))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
actionBuilder.newAction(ActionDefinition.EXAM_SECURITY_KEY_SAVE_SETTINGS)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(action -> this.saveSettings(action, form.getForm()))
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.publish()
|
||||
|
||||
.newAction(ActionDefinition.EXAM_SECURITY_KEY_CANCEL_MODIFY)
|
||||
.withExec(this.pageService.backToCurrentFunction())
|
||||
.publish()
|
||||
|
||||
.newAction(ActionDefinition.EXAM_SECURITY_KEY_SHOW_ADD_GRANT_POPUP)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
connectionInfoTable::getMultiSelection,
|
||||
action -> this.addSecurityKeyGrantPopup.showGrantPopup(
|
||||
action,
|
||||
connectionInfoTable.getSingleSelectedROWData()),
|
||||
APP_SIG_KEY_LIST_EMPTY_SELECTION_TEXT_KEY)
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.noEventPropagation()
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_SECURITY_KEY_SHOW_GRANT_POPUP)
|
||||
.withEntityKey(entityKey)
|
||||
.withSelect(
|
||||
grantsList::getMultiSelection,
|
||||
action -> this.securityKeyGrantPopup.showGrantPopup(action,
|
||||
grantsList.getSingleSelectedROWData()),
|
||||
GRANT_LIST_EMPTY_SELECTION_TEXT_KEY)
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.noEventPropagation()
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_SECURITY_KEY_DELETE_GRANT)
|
||||
.withConfirm(action -> GRANT_LIST_DELETE_CONFORM)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
grantsList::getMultiSelection,
|
||||
this::deleteGrant,
|
||||
GRANT_LIST_EMPTY_SELECTION_TEXT_KEY)
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.publish(false)
|
||||
|
||||
;
|
||||
}
|
||||
|
||||
private PageAction saveSettings(final PageAction action, final Form form) {
|
||||
final String enable = form.getFieldValue(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED);
|
||||
final String threshold = form.getFieldValue(Exam.ADDITIONAL_ATTR_STATISTICAL_GRANT_COUNT_THRESHOLD);
|
||||
final EntityKey entityKey = action.getEntityKey();
|
||||
|
||||
this.pageService
|
||||
.getRestService()
|
||||
.getBuilder(SaveAppSignatureKeySettings.class)
|
||||
.withURIVariable(API.PARAM_PARENT_MODEL_ID, entityKey.modelId)
|
||||
.withFormParam(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED, enable)
|
||||
.withFormParam(Exam.ADDITIONAL_ATTR_STATISTICAL_GRANT_COUNT_THRESHOLD, threshold)
|
||||
.call()
|
||||
.onError(error -> action.pageContext().notifySaveError(EntityType.EXAM, error));
|
||||
return action;
|
||||
}
|
||||
|
||||
private PageAction deleteGrant(final PageAction action) {
|
||||
final EntityKey parentEntityKey = action.getParentEntityKey();
|
||||
final EntityKey singleSelection = action.getSingleSelection();
|
||||
this.pageService.getRestService()
|
||||
.getBuilder(DeleteSecurityKeyGrant.class)
|
||||
.withURIVariable(API.PARAM_PARENT_MODEL_ID, parentEntityKey.modelId)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, singleSelection.modelId)
|
||||
.call()
|
||||
.onError(error -> action.pageContext().notifyUnexpectedError(error));
|
||||
|
||||
return action.withEntityKey(parentEntityKey);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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.gui.content.exam;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class SecurityKeyGrantPopup {
|
||||
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grant.title");
|
||||
private static final LocTextKey TITLE_TEXT_FORM_SIGNATURE =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grant.key");
|
||||
private static final LocTextKey TITLE_TEXT_FORM_TAG =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grant.tag");
|
||||
private static final LocTextKey TITLE_TEXT_FORM_TYPE =
|
||||
new LocTextKey("sebserver.exam.signaturekey.grant.type");
|
||||
|
||||
private final PageService pageService;
|
||||
|
||||
public SecurityKeyGrantPopup(final PageService pageService) {
|
||||
this.pageService = pageService;
|
||||
}
|
||||
|
||||
public PageAction showGrantPopup(final PageAction action, final SecurityKey securityKey) {
|
||||
|
||||
final PopupComposer popupComposer = new PopupComposer(this.pageService, securityKey);
|
||||
try {
|
||||
final ModalInputDialog<FormHandle<?>> dialog =
|
||||
new ModalInputDialog<>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
this.pageService.getWidgetFactory());
|
||||
dialog.setDialogWidth(800);
|
||||
|
||||
dialog.open(
|
||||
TITLE_TEXT_KEY,
|
||||
action.pageContext(),
|
||||
popupComposer::compose);
|
||||
|
||||
} catch (final Exception e) {
|
||||
action.pageContext().notifyUnexpectedError(e);
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
private final class PopupComposer {
|
||||
|
||||
private final PageService pageService;
|
||||
private final SecurityKey securityKey;
|
||||
|
||||
protected PopupComposer(final PageService pageService, final SecurityKey securityKey) {
|
||||
this.pageService = pageService;
|
||||
this.securityKey = securityKey;
|
||||
}
|
||||
|
||||
public void compose(final PageContext pageContext) {
|
||||
|
||||
this.pageService.formBuilder(pageContext)
|
||||
.readonly(true)
|
||||
.addField(FormBuilder.text(
|
||||
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_KEY_VALUE,
|
||||
TITLE_TEXT_FORM_SIGNATURE,
|
||||
String.valueOf(this.securityKey.key))
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG,
|
||||
TITLE_TEXT_FORM_TAG,
|
||||
this.securityKey.tag))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_KEY_TYPE,
|
||||
TITLE_TEXT_FORM_TYPE,
|
||||
this.securityKey.keyType.name()))
|
||||
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -234,12 +234,13 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
NOTIFICATION_LIST_TITLE_KEY,
|
||||
NOTIFICATION_LIST_TITLE_TOOLTIP_KEY);
|
||||
|
||||
final EntityTable<ClientNotification> notificationTable = this.pageService.remoteListTableBuilder(
|
||||
restService.getRestCall(GetPendingClientNotifications.class),
|
||||
EntityType.CLIENT_EVENT)
|
||||
.withRestCallAdapter(builder -> builder.withURIVariable(
|
||||
API.PARAM_PARENT_MODEL_ID,
|
||||
parentEntityKey.modelId)
|
||||
final EntityTable<ClientNotification> notificationTable = this.pageService
|
||||
.remoteListTableBuilder(
|
||||
restService.getRestCall(GetPendingClientNotifications.class), EntityType.CLIENT_EVENT)
|
||||
.withRestCallAdapter(builder -> builder
|
||||
.withURIVariable(
|
||||
API.PARAM_PARENT_MODEL_ID,
|
||||
parentEntityKey.modelId)
|
||||
.withURIVariable(
|
||||
API.EXAM_API_SEB_CONNECTION_TOKEN,
|
||||
connectionToken))
|
||||
|
@ -409,7 +410,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
if (securityKey.id < 0) {
|
||||
if (securityKey.id == null || securityKey.id < 0) {
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_GRANT_SIGNATURE_KEY)
|
||||
.withParentEntityKey(parentEntityKey)
|
||||
|
|
|
@ -61,7 +61,7 @@ public class SignatureKeyGrantPopup {
|
|||
new ModalInputDialog<>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
this.pageService.getWidgetFactory());
|
||||
dialog.setDialogWidth(700);
|
||||
dialog.setDialogWidth(800);
|
||||
|
||||
final Predicate<FormHandle<?>> applyGrant = formHandle -> applyGrant(
|
||||
pageContext,
|
||||
|
@ -104,15 +104,18 @@ public class SignatureKeyGrantPopup {
|
|||
final PageContext formContext = this.pageContext.copyOf(parent);
|
||||
|
||||
final FormHandle<?> form = this.pageService.formBuilder(formContext)
|
||||
.addField(FormBuilder.text(
|
||||
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG,
|
||||
TITLE_TEXT_FORM_TAG,
|
||||
this.securityKey.tag))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_KEY_VALUE,
|
||||
TITLE_TEXT_FORM_SIGNATURE,
|
||||
String.valueOf(this.securityKey.key))
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG,
|
||||
TITLE_TEXT_FORM_TAG,
|
||||
this.securityKey.tag))
|
||||
|
||||
.build();
|
||||
|
||||
return () -> form;
|
||||
|
@ -133,7 +136,13 @@ public class SignatureKeyGrantPopup {
|
|||
.withURIVariable(API.PARAM_MODEL_ID, connectionKey.modelId)
|
||||
.withFormBinding(formHandle.getFormBinding())
|
||||
.call()
|
||||
.onError(formHandle::handleError)
|
||||
.onError(error -> {
|
||||
if (error.getMessage().contains("\"messageCode\":\"1010\"")) {
|
||||
pageContext.publishInfo(new LocTextKey("sebserver.monitoring.signaturegrant.message.granted"));
|
||||
} else {
|
||||
formHandle.handleError(error);
|
||||
}
|
||||
})
|
||||
.hasValue();
|
||||
}
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ public abstract class FieldBuilder<T> {
|
|||
WidgetFactory.ImageIcon.MANDATORY,
|
||||
infoGrid,
|
||||
MANDATORY_TEXT_KEY);
|
||||
mandatory.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
|
||||
mandatory.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, false, false));
|
||||
}
|
||||
|
||||
return infoGrid;
|
||||
|
|
|
@ -341,7 +341,8 @@ public interface PageService {
|
|||
|
||||
<T extends ModelIdAware> TableBuilder<T> staticListTableBuilder(final List<T> staticList, EntityType entityType);
|
||||
|
||||
<T extends ModelIdAware> TableBuilder<T> remoteListTableBuilder(RestCall<Collection<T>> apiCall,
|
||||
<T extends ModelIdAware> TableBuilder<T> remoteListTableBuilder(
|
||||
RestCall<Collection<T>> apiCall,
|
||||
EntityType entityType);
|
||||
|
||||
/** Get a new PageActionBuilder for a given PageContext.
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.gui.service.remote.webservice.api.exam;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class GetClientConnections extends RestCall<Collection<ClientConnection>> {
|
||||
|
||||
public GetClientConnections() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_LIST,
|
||||
EntityType.CLIENT_CONNECTION,
|
||||
new TypeReference<Collection<ClientConnection>>() {
|
||||
}),
|
||||
HttpMethod.GET,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.SEB_CLIENT_CONNECTION_ENDPOINT
|
||||
+ API.LIST_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.gui.service.remote.webservice.api.exam.seckey;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class AddSecurityKeyGrant extends RestCall<SecurityKey> {
|
||||
|
||||
public AddSecurityKeyGrant() {
|
||||
super(new TypeKey<>(
|
||||
CallType.NEW,
|
||||
EntityType.SEB_SECURITY_KEY_REGISTRY,
|
||||
new TypeReference<SecurityKey>() {
|
||||
}),
|
||||
HttpMethod.POST,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.EXAM_ADMINISTRATION_ENDPOINT
|
||||
+ API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_GRANTS_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.gui.service.remote.webservice.api.exam.seckey;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class DeleteSecurityKeyGrant extends RestCall<EntityKey> {
|
||||
|
||||
public DeleteSecurityKeyGrant() {
|
||||
super(new TypeKey<>(
|
||||
CallType.DELETE,
|
||||
EntityType.SEB_SECURITY_KEY_REGISTRY,
|
||||
new TypeReference<EntityKey>() {
|
||||
}),
|
||||
HttpMethod.DELETE,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.EXAM_ADMINISTRATION_ENDPOINT
|
||||
+ API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_GRANTS_PATH_SEGMENT
|
||||
+ API.MODEL_ID_VAR_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.gui.service.remote.webservice.api.exam.seckey;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.AppSignatureKeyInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class GetAppSignatureKeyInfo extends RestCall<Collection<AppSignatureKeyInfo>> {
|
||||
|
||||
public GetAppSignatureKeyInfo() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_SINGLE,
|
||||
EntityType.SEB_SECURITY_KEY_REGISTRY,
|
||||
new TypeReference<Collection<AppSignatureKeyInfo>>() {
|
||||
}),
|
||||
HttpMethod.GET,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.EXAM_ADMINISTRATION_ENDPOINT
|
||||
+ API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_INFO_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.gui.service.remote.webservice.api.exam.seckey;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class GetAppSignatureKeys extends RestCall<Collection<SecurityKey>> {
|
||||
|
||||
public GetAppSignatureKeys() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_LIST,
|
||||
EntityType.SEB_SECURITY_KEY_REGISTRY,
|
||||
new TypeReference<Collection<SecurityKey>>() {
|
||||
}),
|
||||
HttpMethod.GET,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.EXAM_ADMINISTRATION_ENDPOINT
|
||||
+ API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_GRANTS_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.gui.service.remote.webservice.api.exam.seckey;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class SaveAppSignatureKeySettings extends RestCall<Exam> {
|
||||
|
||||
public SaveAppSignatureKeySettings() {
|
||||
super(new TypeKey<>(
|
||||
CallType.SAVE,
|
||||
EntityType.SEB_SECURITY_KEY_REGISTRY,
|
||||
new TypeReference<Exam>() {
|
||||
}),
|
||||
HttpMethod.POST,
|
||||
MediaType.APPLICATION_JSON,
|
||||
API.EXAM_ADMINISTRATION_ENDPOINT
|
||||
+ API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_INFO_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
}
|
|
@ -672,7 +672,8 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
|
|||
boolean push(final ClientMonitoringData monitoringData) {
|
||||
this.dataChanged = this.monitoringData == null ||
|
||||
this.monitoringData.status != monitoringData.status ||
|
||||
this.monitoringData.missingPing != monitoringData.missingPing;
|
||||
this.monitoringData.missingPing != monitoringData.missingPing ||
|
||||
this.monitoringData.missingGrant != monitoringData.missingGrant;
|
||||
this.indicatorValueChanged = this.monitoringData == null ||
|
||||
(this.monitoringData.status.clientActiveStatus
|
||||
&& !this.monitoringData.indicatorValuesEquals(monitoringData));
|
||||
|
|
|
@ -205,6 +205,8 @@ public class EntityTable<ROW extends ModelIdAware> {
|
|||
if (selection != null) {
|
||||
this.pageService.executePageAction(
|
||||
defaultAction.withEntityKey(selection));
|
||||
} else {
|
||||
this.pageService.executePageAction(defaultAction);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -217,8 +219,7 @@ public class EntityTable<ROW extends ModelIdAware> {
|
|||
});
|
||||
|
||||
this.table.addListener(SWT.Selection, event -> this.notifySelectionChange());
|
||||
|
||||
this.navigator = (pageSize > 0) ? new TableNavigator(this) : new TableNavigator();
|
||||
this.navigator = new TableNavigator(this);
|
||||
|
||||
createTableColumns();
|
||||
this.pageNumber = initCurrentPageFromUserAttr();
|
||||
|
|
|
@ -302,8 +302,11 @@ public class WidgetFactory {
|
|||
return defaultPageLayout;
|
||||
}
|
||||
|
||||
public void addFormSubContextHeader(final Composite parent, final LocTextKey titleTextKey,
|
||||
public void addFormSubContextHeader(
|
||||
final Composite parent,
|
||||
final LocTextKey titleTextKey,
|
||||
final LocTextKey tooltipTextKey) {
|
||||
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.BOTTOM, true, false);
|
||||
gridData.horizontalIndent = 8;
|
||||
gridData.verticalIndent = 10;
|
||||
|
|
|
@ -163,11 +163,12 @@ public interface ClientConnectionDAO extends
|
|||
* @return Result refer to the given Exam or to an error when happened. */
|
||||
Result<Exam> deleteClientIndicatorValues(Exam exam);
|
||||
|
||||
/** Get all client connection records for an exam.
|
||||
/** Get all client connection records for exam security key check.
|
||||
* Equals to all in state ACTIVE or CLOSED
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @return Result refer to a collection of client connection records or to an error when happened */
|
||||
Result<Collection<ClientConnectionRecord>> getAllConnectionRecordsForExam(Long examId);
|
||||
Result<Collection<ClientConnectionRecord>> getsecurityKeyConnectionRecords(Long examId);
|
||||
|
||||
/** Get all client connection identifiers for an exam.
|
||||
*
|
||||
|
|
|
@ -22,17 +22,46 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamTemplateDeleti
|
|||
/** Concrete EntityDAO interface of SecurityKeyRegistry entities */
|
||||
public interface SecurityKeyRegistryDAO extends EntityDAO<SecurityKey, SecurityKey> {
|
||||
|
||||
/** Use this to make a copy of an existing security key registry entry for the given exam.
|
||||
* The existing registry entry must be a global or one or one of an exam template.
|
||||
*
|
||||
* @param keyId The security key registry id.
|
||||
* @param examId The exam identifier for the security key copy
|
||||
* @return Result refer to the newly created SecurityKey or to an error when happened. */
|
||||
Result<SecurityKey> registerCopyForExam(Long keyId, Long examId);
|
||||
|
||||
/** Use this to make a copy of r an existing security key registry entry for a given exam template.
|
||||
* The existing registry entry must be a global one or one of an exam.
|
||||
*
|
||||
* @param keyId The security key registry id.
|
||||
* @param examTemplateId The exam template identifier for the new security key copy
|
||||
* @return Result refer to the newly created SecurityKey or the an error when happened */
|
||||
Result<SecurityKey> registerCopyForExamTemplate(Long keyId, Long examTemplateId);
|
||||
|
||||
/** Used to get all security key registry entries of given institution, exam and type.
|
||||
*
|
||||
* @param institutionId The institution identifier
|
||||
* @param examId The exam identifier
|
||||
* @param type The type of the security key
|
||||
* @return Result refer to collection of all matching security key registry entries or to an error when happened */
|
||||
Result<Collection<SecurityKey>> getAll(Long institutionId, Long examId, KeyType type);
|
||||
|
||||
/** Used to delete a given security key registry entry.
|
||||
*
|
||||
* @param keyId The security key registry entry identifier
|
||||
* @return Result refer to the EntityKey of the deleted registry entry or to an error when happened */
|
||||
Result<EntityKey> delete(Long keyId);
|
||||
|
||||
/** Internally used to notify exam deletion to delete all registry entries regarded to the deleted exam.
|
||||
*
|
||||
* @param event The ExamDeletionEvent fired on exam deletion */
|
||||
@EventListener(ExamDeletionEvent.class)
|
||||
void notifyExamDeletion(ExamDeletionEvent event);
|
||||
|
||||
/** Internally used to notify exam template deletion to delete all registry entries regarded to the deleted exam
|
||||
* template
|
||||
*
|
||||
* @param event ExamTemplateDeletionEvent fired on exam template deletion */
|
||||
@EventListener(ExamTemplateDeletionEvent.class)
|
||||
void notifyExamTemplateDeletion(ExamTemplateDeletionEvent event);
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.AdditionalAttribu
|
|||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.AdditionalAttributeRecordMapper;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.NoResourceFoundException;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
|
@ -80,7 +81,9 @@ public class AdditionalAttributesDAOImpl implements AdditionalAttributesDAO {
|
|||
.execute()
|
||||
.stream()
|
||||
.findAny()
|
||||
.orElse(null));
|
||||
.orElseThrow(() -> new NoResourceFoundException(
|
||||
EntityType.ADDITIONAL_ATTRIBUTES,
|
||||
attributeName)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -764,12 +764,15 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
|||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<ClientConnectionRecord>> getAllConnectionRecordsForExam(final Long examId) {
|
||||
public Result<Collection<ClientConnectionRecord>> getsecurityKeyConnectionRecords(final Long examId) {
|
||||
return Result.tryCatch(() -> this.clientConnectionRecordMapper
|
||||
.selectByExample()
|
||||
.where(
|
||||
ClientConnectionRecordDynamicSqlSupport.examId,
|
||||
SqlBuilder.isEqualTo(examId))
|
||||
.and(
|
||||
ClientConnectionRecordDynamicSqlSupport.status,
|
||||
SqlBuilder.isIn(ClientConnection.SECURE_STATES))
|
||||
.build()
|
||||
.execute());
|
||||
}
|
||||
|
|
|
@ -24,9 +24,9 @@ import org.springframework.context.annotation.Lazy;
|
|||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey.KeyType;
|
||||
|
@ -273,6 +273,9 @@ public class SecurityKeyRegistryDAOImpl implements SecurityKeyRegistryDAO {
|
|||
.and(
|
||||
SecurityKeyRegistryRecordDynamicSqlSupport.keyType,
|
||||
isEqualToWhenPresent((type == null) ? null : type.name()))
|
||||
.and(
|
||||
SecurityKeyRegistryRecordDynamicSqlSupport.examId,
|
||||
isNull())
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
|
@ -400,9 +403,10 @@ public class SecurityKeyRegistryDAOImpl implements SecurityKeyRegistryDAO {
|
|||
|
||||
private void checkUniqueKey(final SecurityKey key) {
|
||||
if (getGrantOr(key).getOr(key) != key) {
|
||||
throw new FieldValidationException(
|
||||
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG,
|
||||
"securityKey:keyValue:alreadyGranted");
|
||||
throw new APIMessageException(APIMessage.ErrorMessage.ILLEGAL_API_ARGUMENT.of("Already granted"));
|
||||
// throw new FieldValidationException(
|
||||
// Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG,
|
||||
// "securityKey:keyValue:alreadyGranted");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,21 @@ public interface ExamAdminService {
|
|||
* @return Result refer to the created exam or to an error when happened */
|
||||
Result<Exam> saveLMSAttributes(Exam exam);
|
||||
|
||||
/** Saves the security key settings for an specific exam.
|
||||
*
|
||||
* @param institutionId The institution identifier
|
||||
* @param examId The exam identifier
|
||||
* @param enabled The enabled setting that indicates if the security key check is enabled or not
|
||||
* @param statThreshold the statistical SEB client connection number grant threshold
|
||||
* @return Result refer to the exam with the new settings (additional attributes) or to an error when happened */
|
||||
Result<Exam> saveSecurityKeySettings(
|
||||
Long institutionId,
|
||||
Long examId,
|
||||
Boolean enabled,
|
||||
Integer statThreshold);
|
||||
|
||||
Result<String> getAppSignatureKeySalt(Long institutionId, Long examId);
|
||||
|
||||
/** Applies all additional SEB restriction attributes that are defined by the
|
||||
* type of the LMS of a given Exam to this given Exam.
|
||||
*
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
@ -35,10 +36,12 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
|||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
|
||||
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.dao.NoResourceFoundException;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||
|
@ -81,6 +84,57 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
return this.examDAO.byPK(examId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> saveSecurityKeySettings(
|
||||
final Long institutionId,
|
||||
final Long examId,
|
||||
final Boolean enabled,
|
||||
final Integer statThreshold) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
if (enabled != null) {
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED,
|
||||
String.valueOf(enabled))
|
||||
.onError(error -> log.error("Failed to store ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED: ",
|
||||
error));
|
||||
}
|
||||
if (statThreshold != null) {
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
Exam.ADDITIONAL_ATTR_STATISTICAL_GRANT_COUNT_THRESHOLD,
|
||||
String.valueOf(statThreshold))
|
||||
.onError(error -> log
|
||||
.error("Failed to store ADDITIONAL_ATTR_STATISTICAL_GRANT_COUNT_THRESHOLD: ", error));
|
||||
}
|
||||
|
||||
}).flatMap(v -> this.examDAO.byPK(examId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> getAppSignatureKeySalt(final Long institutionId, final Long examId) {
|
||||
return this.additionalAttributesDAO.getAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_SALT)
|
||||
.onErrorDo(error -> {
|
||||
if (error instanceof NoResourceFoundException) {
|
||||
final CharSequence salt = KeyGenerators.string().generateKey();
|
||||
return this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_SALT, salt.toString()).getOrThrow();
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Unexpected error while trying to get AppSigKey Salt for Exam: " + examId, error);
|
||||
}
|
||||
})
|
||||
.map(AdditionalAttributeRecord::getValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> applyAdditionalSEBRestrictions(final Exam exam) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
|
|
@ -12,40 +12,79 @@ import java.util.Collection;
|
|||
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.AppSignatureKeyInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityCheckResult;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey.KeyType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
||||
public interface SecurityKeyService {
|
||||
|
||||
/** This attribute name is used to store the App-Signature-Key given by a SEB Client */
|
||||
public static final String ADDITIONAL_ATTR_APP_SIGNATURE_KEY = "APP_SIGNATURE_KEY";
|
||||
/** Get the stored App-Signature-Key of a SEB connection within a SecurityKey container.
|
||||
*
|
||||
* @param institutionId The institution identifier
|
||||
* @param connectionId The SEB connection identifier
|
||||
* @return Result refer to the App-Signature-Key of the SEB client connection or to an error when happened */
|
||||
Result<SecurityKey> getAppSignatureKey(Long institutionId, Long connectionId);
|
||||
|
||||
Result<SecurityKey> getSecurityKeyOfConnection(Long institutionId, Long connectionId);
|
||||
/** Get a list of all different SEB App-Signature-Key for a given exam with also the number of SEB
|
||||
* clients that has propagated the respective App-Signature-Key
|
||||
*
|
||||
* @param institutionId The institution identifier
|
||||
* @param examId The exam identifier
|
||||
* @return Result refer to the list of AppSignatureKeyInfo for the given exam or to an error when happened */
|
||||
Result<Collection<AppSignatureKeyInfo>> getAppSignatureKeyInfo(Long institutionId, Long examId);
|
||||
|
||||
Result<AppSignatureKeyInfo> getAppSignaturesInfo(Long institutionId, Long examId);
|
||||
|
||||
Result<Collection<SecurityKey>> getPlainAppSignatureKeyGrants(Long institutionId, Long examId);
|
||||
/** Get a list of all security key registry entries of for given institution and exam.
|
||||
*
|
||||
* @param institutionId The institution identifier
|
||||
* @param examId The exam identifier
|
||||
* @param type The key type filter criteria
|
||||
* @return Result refer to the list of security key registry entries or to an error when happened */
|
||||
Result<Collection<SecurityKey>> getSecurityKeyEntries(Long institutionId, Long examId, KeyType type);
|
||||
|
||||
/** Register a new security key entry in the registry.
|
||||
*
|
||||
* @param key The security key data
|
||||
* @return Result refer to the newly created and stored security key entry or to an error when happened */
|
||||
Result<SecurityKey> registerSecurityKey(SecurityKey key);
|
||||
|
||||
/** Register SEB client connection App-Signature-Key as a new global security key registry entry
|
||||
* This is equivalent to make a global grant for specified App-Signature-Key of given SEB client connection.
|
||||
*
|
||||
* @param institutionId The institution identifier
|
||||
* @param connectionId The client connection identifier
|
||||
* @param tag A Tag for user identification of the grant within the registry
|
||||
* @return Result refer to the newly created security key entry or to an error when happened */
|
||||
Result<SecurityKey> registerGlobalAppSignatureKey(Long institutionId, Long connectionId, String tag);
|
||||
|
||||
/** Register SEB client connection App-Signature-Key as a new exam based security key registry entry
|
||||
* This is equivalent to make a exam specific grant for specified App-Signature-Key of given SEB client connection.
|
||||
*
|
||||
* @param institutionId The institution identifier
|
||||
* @param examId The exam identifier for the exam based grant
|
||||
* @param connectionId The client connection identifier
|
||||
* @param tag A Tag for user identification of the grant within the registry
|
||||
* @return Result refer to the newly created security key entry or to an error when happened */
|
||||
Result<SecurityKey> registerExamAppSignatureKey(Long institutionId, Long examId, Long connectionId, String tag);
|
||||
|
||||
Result<SecurityCheckResult> applyAppSignatureCheck(
|
||||
Long institutionId,
|
||||
Long examId,
|
||||
String connectionToken,
|
||||
String appSignatureKey);
|
||||
|
||||
/** Used to apply a SEB client App-signature-Key check for a given App-Signature-Key sent by the SEB.
|
||||
* Note: This also stores the given App-Signature-Key sent by SEB if not already stored for the SEB connection.
|
||||
*
|
||||
* @param clientConnection The SEB client connection token
|
||||
* @param appSignatureKey The App-Signature-Key sent by the SEB client
|
||||
* @return true if the check was successful and the SEB has a grant, false otherwise */
|
||||
boolean checkAppSignatureKey(ClientConnection clientConnection, String appSignatureKey);
|
||||
|
||||
/** Used to process an update of the App-Signature-Key grant for all SEB connection within given
|
||||
* exam that has not been already granted.
|
||||
*
|
||||
* @param examId The exam identifier */
|
||||
void updateAppSignatureKeyGrants(Long examId);
|
||||
|
||||
Result<SecurityKey> getDecrypted(SecurityKey key);
|
||||
|
||||
Result<EntityKey> deleteSecurityKeyGrant(String keyModelId);
|
||||
/** Delete a given security key form the registry.
|
||||
*
|
||||
* @param keyId The security key registry entry identifier
|
||||
* @return Result refer to the EntityKey of the delete registry entry or to an error when happened. */
|
||||
Result<EntityKey> deleteSecurityKeyGrant(Long keyId);
|
||||
|
||||
}
|
||||
|
|
|
@ -8,18 +8,19 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.institution.impl;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
@ -33,17 +34,18 @@ import ch.ethz.seb.sebserver.gbl.model.institution.SecurityCheckResult;
|
|||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey.KeyType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Pair;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SecurityKeyRegistryDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ClientConnectionDataInternal;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCacheService;
|
||||
|
||||
@Lazy
|
||||
|
@ -74,42 +76,43 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<SecurityKey> getSecurityKeyOfConnection(final Long institutionId, final Long connectionId) {
|
||||
public Result<SecurityKey> getAppSignatureKey(final Long institutionId, final Long connectionId) {
|
||||
return this.clientConnectionDAO.byPK(connectionId)
|
||||
.map(connection -> new SecurityKey(
|
||||
null,
|
||||
institutionId,
|
||||
KeyType.APP_SIGNATURE_KEY,
|
||||
decryptStoredSignatureForConnection(connection),
|
||||
getHashedSignature(connection),
|
||||
connection.sebVersion,
|
||||
null, null))
|
||||
.flatMap(this.securityKeyRegistryDAO::getGrantOr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<AppSignatureKeyInfo> getAppSignaturesInfo(final Long institutionId, final Long examId) {
|
||||
public Result<Collection<AppSignatureKeyInfo>> getAppSignatureKeyInfo(final Long institutionId, final Long examId) {
|
||||
return Result.tryCatch(() -> {
|
||||
final Map<String, Set<Long>> keyMapping = new HashMap<>();
|
||||
|
||||
this.clientConnectionDAO
|
||||
.getAllConnectionRecordsForExam(examId)
|
||||
return this.clientConnectionDAO
|
||||
.getsecurityKeyConnectionRecords(examId)
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.forEach(rec -> keyMapping.computeIfAbsent(
|
||||
this.decryptStoredSignatureForConnection(
|
||||
rec.getId(),
|
||||
rec.getConnectionToken()),
|
||||
s -> new HashSet<>()).add(rec.getId()));
|
||||
|
||||
return new AppSignatureKeyInfo(institutionId, examId, keyMapping);
|
||||
.reduce(
|
||||
new HashMap<String, Map<Long, String>>(),
|
||||
this::reduceAppSecKey,
|
||||
Utils::<String, Map<Long, String>> mergeMap)
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(m -> new AppSignatureKeyInfo(institutionId, examId, m.getKey(), m.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Collection<SecurityKey>> getPlainAppSignatureKeyGrants(final Long institutionId, final Long examId) {
|
||||
public Result<Collection<SecurityKey>> getSecurityKeyEntries(final Long institutionId, final Long examId,
|
||||
final KeyType type) {
|
||||
return this.securityKeyRegistryDAO
|
||||
.getAll(institutionId, examId, KeyType.APP_SIGNATURE_KEY)
|
||||
.map(this::decryptAll);
|
||||
.getAll(institutionId, examId, type)
|
||||
.map(this::getKeysForRead);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -158,44 +161,6 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
|||
tag, examId, null)).getOrThrow());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<SecurityCheckResult> applyAppSignatureCheck(
|
||||
final Long institutionId,
|
||||
final Long examId,
|
||||
final String connectionToken,
|
||||
final String appSignatureKey) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Apply app-signature-key check for connection: {}", connectionToken);
|
||||
}
|
||||
|
||||
return this.securityKeyRegistryDAO
|
||||
.getAll(institutionId, examId, KeyType.APP_SIGNATURE_KEY)
|
||||
.map(all -> {
|
||||
final String decryptedSignature = decryptSignature(examId, connectionToken, appSignatureKey);
|
||||
final List<SecurityKey> matches = all.stream()
|
||||
.map(this::decryptGrantedKey)
|
||||
.filter(pair -> pair != null && Objects.equals(decryptedSignature, pair.a))
|
||||
.map(Pair::getB)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (matches == null || matches.isEmpty()) {
|
||||
return statisticalCheck(examId, decryptedSignature);
|
||||
} else {
|
||||
return new SecurityCheckResult(
|
||||
matches.stream()
|
||||
.filter(key -> key.examId != null)
|
||||
.findFirst()
|
||||
.isPresent(),
|
||||
matches.stream()
|
||||
.filter(key -> key.examId == null)
|
||||
.findFirst()
|
||||
.isPresent(),
|
||||
false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkAppSignatureKey(
|
||||
final ClientConnection clientConnection,
|
||||
|
@ -237,7 +202,12 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
|||
clientConnection.examId,
|
||||
clientConnection.connectionToken,
|
||||
signature)
|
||||
.map(SecurityCheckResult::hasAnyGrant)
|
||||
.map(result -> {
|
||||
if (result.statisticallyGranted) {
|
||||
this.updateAppSignatureKeyGrants(clientConnection.examId);
|
||||
}
|
||||
return result.hasAnyGrant();
|
||||
})
|
||||
.onError(error -> log.error("Failed to applyAppSignatureCheck: ", error))
|
||||
.getOr(false);
|
||||
|
||||
|
@ -249,19 +219,6 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<SecurityKey> getDecrypted(final SecurityKey key) {
|
||||
return this.cryptor.decrypt(key.key)
|
||||
.map(dKey -> new SecurityKey(
|
||||
key.id,
|
||||
key.institutionId,
|
||||
key.keyType,
|
||||
dKey,
|
||||
key.tag,
|
||||
key.examId,
|
||||
key.examTemplateId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAppSignatureKeyGrants(final Long examId) {
|
||||
if (examId == null) {
|
||||
|
@ -271,30 +228,11 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
|||
try {
|
||||
|
||||
this.clientConnectionDAO
|
||||
.getConnectionTokens(examId)
|
||||
.getsecurityKeyConnectionRecords(examId)
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.forEach(token -> {
|
||||
final ClientConnectionDataInternal clientConnection =
|
||||
this.examSessionCacheService.getClientConnection(token);
|
||||
if (!clientConnection.clientConnection.isSecurityCheckGranted()) {
|
||||
if (this.checkAppSignatureKey(clientConnection.clientConnection, null)) {
|
||||
// now granted, update ClientConnection on DB level
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Update app-signature-key grant for client connection: {}", token);
|
||||
}
|
||||
|
||||
this.clientConnectionDAO
|
||||
.save(new ClientConnection(
|
||||
clientConnection.clientConnection.id, null,
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, true))
|
||||
.onError(error -> log.error("Failed to save ClientConnection grant: ", error))
|
||||
.onSuccess(c -> this.examSessionCacheService.evictClientConnection(token));
|
||||
}
|
||||
}
|
||||
});
|
||||
.filter(rec -> ConnectionStatus.ACTIVE.name().equals(rec.getStatus()))
|
||||
.forEach(this::updateUngrantedConnections);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Unexpected error while trying to update app-signature-key grants: ", e);
|
||||
|
@ -308,9 +246,84 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<EntityKey> deleteSecurityKeyGrant(final String keyModelId) {
|
||||
return Result.tryCatch(() -> Long.parseLong(keyModelId))
|
||||
.flatMap(this.securityKeyRegistryDAO::delete);
|
||||
public Result<EntityKey> deleteSecurityKeyGrant(final Long keyId) {
|
||||
return Result.tryCatch(() -> {
|
||||
final SecurityKey key = this.securityKeyRegistryDAO.byPK(keyId).getOrThrow();
|
||||
|
||||
final String grantedKey = decryptGrantedKey(key).a;
|
||||
this.securityKeyRegistryDAO.delete(keyId).getOrThrow();
|
||||
this.clientConnectionDAO.getsecurityKeyConnectionRecords(key.examId)
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.filter(rec -> ConnectionStatus.ACTIVE.name().equals(rec.getStatus()))
|
||||
.forEach(rec -> {
|
||||
try {
|
||||
final String connectionkey = this.decryptStoredSignatureForConnection(
|
||||
rec.getId(),
|
||||
rec.getConnectionToken());
|
||||
if (grantedKey.equals(connectionkey)) {
|
||||
// we have to re-check here
|
||||
final boolean granted = this.applyAppSignatureCheck(
|
||||
rec.getInstitutionId(),
|
||||
rec.getExamId(),
|
||||
rec.getConnectionToken(),
|
||||
connectionkey).getOrThrow().hasAnyGrant();
|
||||
final Boolean grantedBefore = Utils.fromByte(rec.getSecurityCheckGranted());
|
||||
if (granted != grantedBefore) {
|
||||
// update grant
|
||||
this.clientConnectionDAO
|
||||
.save(new ClientConnection(
|
||||
rec.getId(), null,
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, granted));
|
||||
this.examSessionCacheService.evictClientConnection(rec.getConnectionToken());
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to update security key grant for connection on deletion -> {}", rec, e);
|
||||
}
|
||||
});
|
||||
|
||||
return key.getEntityKey();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private Result<SecurityCheckResult> applyAppSignatureCheck(
|
||||
final Long institutionId,
|
||||
final Long examId,
|
||||
final String connectionToken,
|
||||
final String appSignatureKey) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Apply app-signature-key check for connection: {}", connectionToken);
|
||||
}
|
||||
|
||||
return this.securityKeyRegistryDAO
|
||||
.getAll(institutionId, examId, KeyType.APP_SIGNATURE_KEY)
|
||||
.map(all -> {
|
||||
final String decryptedSignature = decryptSignature(examId, connectionToken, appSignatureKey);
|
||||
final List<SecurityKey> matches = all.stream()
|
||||
.map(this::decryptGrantedKey)
|
||||
.filter(pair -> pair != null && Objects.equals(decryptedSignature, pair.a))
|
||||
.map(Pair::getB)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (matches == null || matches.isEmpty()) {
|
||||
return statisticalCheck(examId, decryptedSignature);
|
||||
} else {
|
||||
return new SecurityCheckResult(
|
||||
matches.stream()
|
||||
.filter(key -> key.examId != null)
|
||||
.findFirst()
|
||||
.isPresent(),
|
||||
matches.stream()
|
||||
.filter(key -> key.examId == null)
|
||||
.findFirst()
|
||||
.isPresent(),
|
||||
false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Pair<String, SecurityKey> decryptGrantedKey(final SecurityKey key) {
|
||||
|
@ -408,12 +421,7 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
|||
}
|
||||
|
||||
private String decryptStoredSignatureForConnection(final ClientConnection cc) {
|
||||
final String signatureKey = getSignatureKeyForConnection(cc);
|
||||
if (StringUtils.isBlank(signatureKey)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return decryptSignatureWithConnectionToken(cc.connectionToken, signatureKey);
|
||||
return decryptStoredSignatureForConnection(cc.id, cc.connectionToken);
|
||||
}
|
||||
|
||||
private String decryptStoredSignatureForConnection(final Long cId, final String cToken) {
|
||||
|
@ -425,12 +433,31 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
|||
return decryptSignatureWithConnectionToken(cToken, signatureKey);
|
||||
}
|
||||
|
||||
private String getHashedSignature(final ClientConnection connection) {
|
||||
return getHashedSignature(connection.id, connection.connectionToken);
|
||||
}
|
||||
|
||||
private String getHashedSignature(final Long cId, final String cToken) {
|
||||
|
||||
final String signatureKey = getSignatureKeyForConnection(cId);
|
||||
if (StringUtils.isBlank(signatureKey)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return getSignatureHash(decryptSignatureWithConnectionToken(cToken, signatureKey));
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to get hashed signature key for read: ", e);
|
||||
return signatureKey;
|
||||
}
|
||||
}
|
||||
|
||||
private void saveSignatureKeyForConnection(final ClientConnection clientConnection, final String appSignatureKey) {
|
||||
this.additionalAttributesDAO
|
||||
.saveAdditionalAttribute(
|
||||
EntityType.CLIENT_CONNECTION,
|
||||
clientConnection.id,
|
||||
ADDITIONAL_ATTR_APP_SIGNATURE_KEY,
|
||||
ClientConnection.ADDITIONAL_ATTR_APP_SIGNATURE_KEY,
|
||||
appSignatureKey)
|
||||
.onError(error -> log.error(
|
||||
"Failed to store App-Signature-Key for clientConnection: {}",
|
||||
|
@ -442,7 +469,7 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
|||
.getAdditionalAttribute(
|
||||
EntityType.CLIENT_CONNECTION,
|
||||
connectionId,
|
||||
ADDITIONAL_ATTR_APP_SIGNATURE_KEY)
|
||||
ClientConnection.ADDITIONAL_ATTR_APP_SIGNATURE_KEY)
|
||||
.map(AdditionalAttributeRecord::getValue)
|
||||
.getOr(null);
|
||||
}
|
||||
|
@ -474,14 +501,6 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
|||
.getOr(1);
|
||||
}
|
||||
|
||||
private Collection<SecurityKey> decryptAll(final Collection<SecurityKey> all) {
|
||||
return all.stream()
|
||||
.map(this::getDecrypted)
|
||||
.filter(Result::hasValue)
|
||||
.map(Result::get)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Result<SecurityKey> encryptInternal(final SecurityKey key) {
|
||||
return Result.tryCatch(() -> new SecurityKey(
|
||||
key.id,
|
||||
|
@ -493,4 +512,76 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
|||
key.examTemplateId));
|
||||
}
|
||||
|
||||
private Collection<SecurityKey> getKeysForRead(final Collection<SecurityKey> keys) {
|
||||
return keys.stream()
|
||||
.map(this::getInternalKeyForRead)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private SecurityKey getInternalKeyForRead(final SecurityKey key) {
|
||||
try {
|
||||
return new SecurityKey(
|
||||
key.id,
|
||||
key.institutionId,
|
||||
key.keyType,
|
||||
getSignatureHash(this.cryptor.decrypt(key.key).getOrThrow()),
|
||||
key.tag,
|
||||
key.examId,
|
||||
key.examTemplateId);
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to internally decrypt security key value: ", e);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
private String getSignatureHash(final CharSequence signature) throws NoSuchAlgorithmException {
|
||||
final MessageDigest hasher = MessageDigest.getInstance("SHA-256");
|
||||
hasher.update(Utils.toByteArray(signature));
|
||||
final String signatureHash = Hex.toHexString(hasher.digest());
|
||||
return signatureHash;
|
||||
}
|
||||
|
||||
private Map<String, Map<Long, String>> reduceAppSecKey(
|
||||
final Map<String, Map<Long, String>> m,
|
||||
final ClientConnectionRecord rec) {
|
||||
|
||||
final Map<Long, String> mapping = m.computeIfAbsent(
|
||||
this.getHashedSignature(rec.getId(), rec.getConnectionToken()),
|
||||
s -> new HashMap<>());
|
||||
|
||||
mapping.put(rec.getId(), rec.getExamUserSessionId());
|
||||
return m;
|
||||
}
|
||||
|
||||
private void updateUngrantedConnections(final ClientConnectionRecord rec) {
|
||||
try {
|
||||
if (!Utils.fromByte(rec.getSecurityCheckGranted())) {
|
||||
final String token = rec.getConnectionToken();
|
||||
if (applyAppSignatureCheck(
|
||||
rec.getInstitutionId(),
|
||||
rec.getExamId(),
|
||||
token,
|
||||
getSignatureKeyForConnection(rec.getId()))
|
||||
.getOrThrow()
|
||||
.hasAnyGrant()) {
|
||||
// now granted, update ClientConnection on DB level
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Update app-signature-key grant for client connection: {}", token);
|
||||
}
|
||||
|
||||
this.clientConnectionDAO
|
||||
.save(new ClientConnection(
|
||||
rec.getId(), null,
|
||||
null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, true))
|
||||
.onError(error -> log.error("Failed to save ClientConnection grant: ",
|
||||
error))
|
||||
.onSuccess(c -> this.examSessionCacheService.evictClientConnection(token));
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to updateAppSignatureKeyGrants for connection: {}", rec, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -141,6 +141,11 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
|
|||
public boolean isPendingNotification() {
|
||||
return BooleanUtils.isTrue(pendingNotification());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMissingGrant() {
|
||||
return BooleanUtils.isFalse(ClientConnectionDataInternal.this.clientConnection.securityCheckGranted);
|
||||
}
|
||||
};
|
||||
|
||||
/** This is a static monitoring connection data wrapper/holder */
|
||||
|
|
|
@ -51,6 +51,7 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
|||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.LmsSetupDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientSessionService;
|
||||
|
@ -63,6 +64,7 @@ public class ExamAPI_V1_Controller {
|
|||
private static final Logger log = LoggerFactory.getLogger(ExamAPI_V1_Controller.class);
|
||||
|
||||
private final LmsSetupDAO lmsSetupDAO;
|
||||
private final ExamAdminService examAdminService;
|
||||
private final ExamSessionService examSessionService;
|
||||
private final SEBClientConnectionService sebClientConnectionService;
|
||||
private final SEBClientSessionService sebClientSessionService;
|
||||
|
@ -72,6 +74,7 @@ public class ExamAPI_V1_Controller {
|
|||
|
||||
protected ExamAPI_V1_Controller(
|
||||
final LmsSetupDAO lmsSetupDAO,
|
||||
final ExamAdminService examAdminService,
|
||||
final ExamSessionService examSessionService,
|
||||
final SEBClientConnectionService sebClientConnectionService,
|
||||
final SEBClientSessionService sebClientSessionService,
|
||||
|
@ -80,6 +83,7 @@ public class ExamAPI_V1_Controller {
|
|||
@Qualifier(AsyncServiceSpringConfig.EXAM_API_EXECUTOR_BEAN_NAME) final Executor executor) {
|
||||
|
||||
this.lmsSetupDAO = lmsSetupDAO;
|
||||
this.examAdminService = examAdminService;
|
||||
this.examSessionService = examSessionService;
|
||||
this.sebClientConnectionService = sebClientConnectionService;
|
||||
this.sebClientSessionService = sebClientSessionService;
|
||||
|
@ -135,6 +139,16 @@ public class ExamAPI_V1_Controller {
|
|||
API.EXAM_API_SEB_CONNECTION_TOKEN,
|
||||
clientConnection.connectionToken);
|
||||
|
||||
if (clientConnection.examId != null) {
|
||||
this.examAdminService
|
||||
.getAppSignatureKeySalt(institutionId, clientConnection.examId)
|
||||
.onSuccess(salt -> response.setHeader(API.EXAM_API_EXAM_SIGNATURE_SALT_HEADER, salt))
|
||||
.onError(error -> log.error(
|
||||
"Failed to get security key salt for connection: {}",
|
||||
clientConnection,
|
||||
error));
|
||||
}
|
||||
|
||||
// Crate list of running exams
|
||||
List<RunningExamInfo> result;
|
||||
if (examId == null) {
|
||||
|
@ -192,7 +206,8 @@ public class ExamAPI_V1_Controller {
|
|||
required = false) final String browserSignatureKey,
|
||||
@RequestParam(name = API.EXAM_API_PARAM_CLIENT_ID, required = false) final String clientId,
|
||||
final Principal principal,
|
||||
final HttpServletRequest request) {
|
||||
final HttpServletRequest request,
|
||||
final HttpServletResponse response) {
|
||||
|
||||
return CompletableFuture.runAsync(
|
||||
() -> {
|
||||
|
@ -200,7 +215,7 @@ public class ExamAPI_V1_Controller {
|
|||
final String remoteAddr = this.getClientAddress(request);
|
||||
final Long institutionId = getInstitutionId(principal);
|
||||
|
||||
this.sebClientConnectionService.updateClientConnection(
|
||||
final ClientConnection clientConnection = this.sebClientConnectionService.updateClientConnection(
|
||||
connectionToken,
|
||||
institutionId,
|
||||
examId,
|
||||
|
@ -212,6 +227,16 @@ public class ExamAPI_V1_Controller {
|
|||
clientId,
|
||||
browserSignatureKey)
|
||||
.getOrThrow();
|
||||
|
||||
if (clientConnection.examId != null) {
|
||||
this.examAdminService
|
||||
.getAppSignatureKeySalt(institutionId, clientConnection.examId)
|
||||
.onSuccess(salt -> response.setHeader(API.EXAM_API_EXAM_SIGNATURE_SALT_HEADER, salt))
|
||||
.onError(error -> log.error(
|
||||
"Failed to get security key salt for connection: {}",
|
||||
clientConnection,
|
||||
error));
|
||||
}
|
||||
},
|
||||
this.executor);
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ import ch.ethz.seb.sebserver.gbl.model.institution.AppSignatureKeyInfo;
|
|||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey.KeyType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
@ -195,11 +196,11 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
|
||||
@RequestMapping(
|
||||
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_GRANTS_PATH_SEGMENT,
|
||||
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_INFO_PATH_SEGMENT,
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public Collection<SecurityKey> getSecurityGrants(
|
||||
@PathVariable(name = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT, required = true) final Long examId,
|
||||
public Collection<AppSignatureKeyInfo> getAppSignatureKeyInfo(
|
||||
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
|
@ -207,7 +208,52 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
|
||||
return this.examDAO.byPK(examId)
|
||||
.flatMap(this::checkReadAccess)
|
||||
.flatMap(exam -> this.securityKeyService.getPlainAppSignatureKeyGrants(institutionId, examId))
|
||||
.flatMap(exam -> this.securityKeyService.getAppSignatureKeyInfo(institutionId, examId))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_INFO_PATH_SEGMENT,
|
||||
method = RequestMethod.POST,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public void saveAppSignatureKeySettings(
|
||||
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||
@RequestParam(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED) final Boolean enableKeyCheck,
|
||||
@RequestParam(Exam.ADDITIONAL_ATTR_STATISTICAL_GRANT_COUNT_THRESHOLD) final Integer threshold) {
|
||||
|
||||
this.examDAO.byPK(examId)
|
||||
.flatMap(this::checkReadAccess)
|
||||
.flatMap(exam -> this.examAdminService.saveSecurityKeySettings(
|
||||
institutionId,
|
||||
examId,
|
||||
enableKeyCheck,
|
||||
threshold))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_GRANTS_PATH_SEGMENT,
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public Collection<SecurityKey> getSecurityKeyEntries(
|
||||
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
|
||||
|
||||
return this.examDAO.byPK(examId)
|
||||
.flatMap(this::checkReadAccess)
|
||||
.flatMap(exam -> this.securityKeyService.getSecurityKeyEntries(
|
||||
institutionId,
|
||||
examId,
|
||||
KeyType.APP_SIGNATURE_KEY))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
|
@ -217,7 +263,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
method = RequestMethod.POST,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public SecurityKey newSecurityGrant(
|
||||
@PathVariable(name = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT, required = true) final Long examId,
|
||||
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
|
@ -238,14 +284,14 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.MODEL_ID_VAR_PATH_SEGMENT
|
||||
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_KEY_GRANTS_PATH_SEGMENT
|
||||
+ API.MODEL_ID_VAR_PATH_SEGMENT,
|
||||
method = RequestMethod.DELETE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public EntityKey deleteSecurityGrant(
|
||||
@PathVariable(name = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT, required = true) final Long examId,
|
||||
@PathVariable(name = API.MODEL_ID_VAR_PATH_SEGMENT, required = true) final String keyId,
|
||||
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
|
||||
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long keyId,
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
|
@ -259,24 +305,6 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
.getOrThrow();
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_ADMINISTRATION_SEB_SECURITY_AS_KEYS_PATH_SEGMENT,
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public AppSignatureKeyInfo getAppSignatureKeyInfo(
|
||||
@PathVariable(name = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT, required = true) final Long examId,
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
|
||||
|
||||
return this.examDAO.byPK(examId)
|
||||
.flatMap(this::checkReadAccess)
|
||||
.flatMap(exam -> this.securityKeyService.getAppSignaturesInfo(institutionId, examId))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
// **** SEB Security Key
|
||||
// ****************************************************************************
|
||||
|
||||
|
|
|
@ -503,7 +503,7 @@ public class ExamMonitoringController {
|
|||
|
||||
checkPrivileges(institutionId, examId);
|
||||
return this.securityKeyService
|
||||
.getSecurityKeyOfConnection(institutionId, connectionId)
|
||||
.getAppSignatureKey(institutionId, connectionId)
|
||||
.getOrThrow();
|
||||
|
||||
}
|
||||
|
|
|
@ -795,7 +795,53 @@ sebserver.exam.proctoring.collecting.open.error=Failed to open the collecting ro
|
|||
sebserver.exam.proctoring.collecting.close.error=Failed to close the collecting room properly.
|
||||
|
||||
sebserver.exam.signaturekey.action.edit=App Signature Key
|
||||
sebserver.exam.signaturekey.action.save=Save Settings
|
||||
sebserver.exam.signaturekey.action.cancel=Cancel and back to Exam
|
||||
sebserver.exam.signaturekey.action.addGrant=Add Security Grant
|
||||
sebserver.exam.signaturekey.action.showGrant=Show Security Grant
|
||||
sebserver.exam.signaturekey.action.deleteGrant=Delete Security Grant
|
||||
sebserver.exam.signaturekey.title=App Signature Key Overview
|
||||
sebserver.exam.signaturekey.form.enabled=Enable App Signature Key Check
|
||||
sebserver.exam.signaturekey.form.enabled.tooltip=Enable the App Signature Key Check for this exam. If disabled no check will be applied
|
||||
sebserver.exam.signaturekey.form.grant.threshold=Statistical Key Check Threshold
|
||||
sebserver.exam.signaturekey.form.grant.threshold.tooltip=If there is no explicit grant registered for a given App Signature Key,<br/>a given key will be considered valid if more then the given number of connected SEB clients has the same key.
|
||||
|
||||
sebserver.exam.signaturekey.keylist.actions=
|
||||
sebserver.exam.signaturekey.keylist.empty=No App Signature Key from SEB Clients available
|
||||
sebserver.exam.signaturekey.keylist.title=App Signature Keys sent by SEB Clients
|
||||
sebserver.exam.signaturekey.keylist.title.tooltip=List of all different App Signature Keys sent by the SEB Clients for this exam
|
||||
sebserver.exam.signaturekey.keylist.key=Key Hash
|
||||
sebserver.exam.signaturekey.keylist.key.tooltip=The App Signature Key sent by some SEB Client(s)
|
||||
sebserver.exam.signaturekey.keylist.clients=Number of SEB Clients
|
||||
sebserver.exam.signaturekey.keylist.clients.tooltip=The number of SEB Clients that sent this key within this exam.
|
||||
sebserver.exam.signaturekey.keylist.clientids=User Session Identifiers
|
||||
sebserver.exam.signaturekey.keylist.clientids.tooltip=List of SEB Client session identifiers of the SEB Client sessions that sent this key
|
||||
sebserver.exam.signaturekey.keylist.pleaseSelect=Please select an App Signature Key from the list.
|
||||
|
||||
sebserver.exam.signaturekey.seb.title=App Signature Key
|
||||
sebserver.exam.signaturekey.seb.add.info=Please set a meaningful Tag Name and use OK to confirm this security key as granted.
|
||||
sebserver.exam.signaturekey.seb.add.signature=Key Hash
|
||||
sebserver.exam.signaturekey.seb.add.tag=Tag Name
|
||||
|
||||
sebserver.exam.signaturekey.list.name=SEB Session ID
|
||||
sebserver.exam.signaturekey.list.info=SEB Client Info
|
||||
sebserver.exam.signaturekey.list.status=Connection Status
|
||||
|
||||
sebserver.exam.signaturekey.grantlist.actions=
|
||||
sebserver.exam.signaturekey.grantlist.title=Security Key Grants
|
||||
sebserver.exam.signaturekey.grantlist.title.tooltip=List of all granted security keys of this exam
|
||||
sebserver.exam.signaturekey.grantlist.empty=There are currently no security key grants
|
||||
sebserver.exam.signaturekey.grantlist.key=Key Hash
|
||||
sebserver.exam.signaturekey.grantlist.key.tooltip=The security key that has been granted for this exam
|
||||
sebserver.exam.signaturekey.grantlist.tag=Tag Name
|
||||
sebserver.exam.signaturekey.grantlist.tag.tooltip=The tag name if the security key grant
|
||||
sebserver.exam.signaturekey.grantlist.pleaseSelect=Please select a security key grant from the list.
|
||||
sebserver.exam.signaturekey.grantlist.delete.confirm=Are you sure to delete this security key grant
|
||||
|
||||
sebserver.exam.signaturekey.grant.title=Security Key Grant
|
||||
sebserver.exam.signaturekey.grant.key=Granted Key Hash
|
||||
sebserver.exam.signaturekey.grant.tag=Tag Name
|
||||
sebserver.exam.signaturekey.grant.type=Key Type
|
||||
|
||||
################################
|
||||
# Connection Configuration
|
||||
|
@ -2041,9 +2087,10 @@ sebserver.monitoring.lock.list.info=SEB Connection Info
|
|||
sebserver.monitoring.lock.noselection=Please select at least one active SEB client connection.
|
||||
|
||||
sebserver.monitoring.signaturegrant.title=Grant App Signature Key
|
||||
sebserver.monitoring.signaturegrant.info=Mark this App Signature Key as granted. Please also choose a meaningful tag.
|
||||
sebserver.monitoring.signaturegrant.signature=App Signature Key
|
||||
sebserver.monitoring.signaturegrant.info=Mark this App Signature Key as granted. Please also choose a meaningful tag name.
|
||||
sebserver.monitoring.signaturegrant.signature=App Signature Key Hash
|
||||
sebserver.monitoring.signaturegrant.tag=Tag
|
||||
sebserver.monitoring.signaturegrant.message.granted=This App Signature Key is already granted for this exam
|
||||
|
||||
################################
|
||||
# Finished Exams
|
||||
|
|
Loading…
Reference in a new issue