SEBSERV-335 implementation Exam
This commit is contained in:
parent
949552bbf7
commit
ea13bb6ca4
30 changed files with 712 additions and 86 deletions
|
@ -155,7 +155,8 @@ public final class API {
|
||||||
public static final String EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT = "/check-imported";
|
public static final String EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT = "/check-imported";
|
||||||
public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_CHAPTERS_PATH_SEGMENT = "/chapters";
|
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_PROCTORING_PATH_SEGMENT = "/proctoring";
|
||||||
public static final String EXAM_ADMINISTRATION_SEB_SECURITY_KEY_GRANTS_PATH_SEGMENT = "/seb-grants";
|
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_INDICATOR_ENDPOINT = "/indicator";
|
public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";
|
||||||
public static final String EXAM_CLIENT_GROUP_ENDPOINT = "/client-group";
|
public static final String EXAM_CLIENT_GROUP_ENDPOINT = "/client-group";
|
||||||
|
@ -200,7 +201,7 @@ public final class API {
|
||||||
public static final String EXAM_MONITORING_INSTRUCTION_ENDPOINT = "/instruction";
|
public static final String EXAM_MONITORING_INSTRUCTION_ENDPOINT = "/instruction";
|
||||||
public static final String EXAM_MONITORING_NOTIFICATION_ENDPOINT = "/notification";
|
public static final String EXAM_MONITORING_NOTIFICATION_ENDPOINT = "/notification";
|
||||||
public static final String EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT = "/disable-connection";
|
public static final String EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT = "/disable-connection";
|
||||||
public static final String EXAM_MONITORING_GRANT_APP_SIGNATURE_KEY_ENDPOINT = "/apply-grant";
|
public static final String EXAM_MONITORING_SIGNATURE_KEY_ENDPOINT = "/signature";
|
||||||
public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states";
|
public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states";
|
||||||
public static final String EXAM_MONITORING_CLIENT_GROUP_FILTER = "hidden-client-group";
|
public static final String EXAM_MONITORING_CLIENT_GROUP_FILTER = "hidden-client-group";
|
||||||
public static final String EXAM_MONITORING_FINISHED_ENDPOINT = "/finishedexams";
|
public static final String EXAM_MONITORING_FINISHED_ENDPOINT = "/finishedexams";
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* 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.gbl.model.institution;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.Domain.SEB_SECURITY_KEY_REGISTRY;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
public class AppSignatureKeyInfo {
|
||||||
|
|
||||||
|
public static final String ATTR_KEY_CONNECTION_MAPPING = "kcMapping";
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@JsonProperty(SEB_SECURITY_KEY_REGISTRY.ATTR_INSTITUTION_ID)
|
||||||
|
public final Long institutionId;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@JsonProperty(SEB_SECURITY_KEY_REGISTRY.ATTR_EXAM_ID)
|
||||||
|
public final Long examId;
|
||||||
|
|
||||||
|
@JsonProperty(ATTR_KEY_CONNECTION_MAPPING)
|
||||||
|
public final Map<String, Set<Long>> keyConnectionMapping;
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
|
||||||
|
this.institutionId = institutionId;
|
||||||
|
this.examId = examId;
|
||||||
|
this.keyConnectionMapping = Utils.immutableMapOf(keyConnectionMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getInstitutionId() {
|
||||||
|
return this.institutionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getExamId() {
|
||||||
|
return this.examId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Set<Long>> getKeyConnectionMapping() {
|
||||||
|
return this.keyConnectionMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(this.examId, this.institutionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
final AppSignatureKeyInfo other = (AppSignatureKeyInfo) obj;
|
||||||
|
return Objects.equals(this.examId, other.examId) && Objects.equals(this.institutionId, other.institutionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("AppSignatureKeyInfo [institutionId=");
|
||||||
|
builder.append(this.institutionId);
|
||||||
|
builder.append(", examId=");
|
||||||
|
builder.append(this.examId);
|
||||||
|
builder.append(", keyConnectionMapping=");
|
||||||
|
builder.append(this.keyConnectionMapping);
|
||||||
|
builder.append("]");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -55,7 +55,7 @@ public final class LmsSetup implements GrantEntity, Activatable {
|
||||||
* Also defines the supports feature(s) for each type of LMS binding. */
|
* Also defines the supports feature(s) for each type of LMS binding. */
|
||||||
public enum LmsType {
|
public enum LmsType {
|
||||||
/** Mockup LMS type used to create test setups */
|
/** Mockup LMS type used to create test setups */
|
||||||
MOCKUP(Features.COURSE_API),
|
MOCKUP(Features.COURSE_API, Features.SEB_RESTRICTION),
|
||||||
/** The Open edX LMS binding features both APIs, course access as well as SEB restriction */
|
/** The Open edX LMS binding features both APIs, course access as well as SEB restriction */
|
||||||
OPEN_EDX(Features.COURSE_API, Features.SEB_RESTRICTION),
|
OPEN_EDX(Features.COURSE_API, Features.SEB_RESTRICTION),
|
||||||
/** The Moodle binding features only the course access API so far */
|
/** The Moodle binding features only the course access API so far */
|
||||||
|
|
|
@ -415,6 +415,17 @@ public enum ActionDefinition {
|
||||||
PageStateDefinitionImpl.EXAM_VIEW,
|
PageStateDefinitionImpl.EXAM_VIEW,
|
||||||
ActionCategory.FORM),
|
ActionCategory.FORM),
|
||||||
|
|
||||||
|
EXAM_SECURITY_KEY_ENABLED(
|
||||||
|
new LocTextKey("sebserver.exam.signaturekey.action.edit"),
|
||||||
|
ImageIcon.SHIELD,
|
||||||
|
PageStateDefinitionImpl.SECURITY_KEY_EDIT,
|
||||||
|
ActionCategory.FORM),
|
||||||
|
EXAM_SECURITY_KEY_DISABLED(
|
||||||
|
new LocTextKey("sebserver.exam.signaturekey.action.edit"),
|
||||||
|
ImageIcon.NO_SHIELD,
|
||||||
|
PageStateDefinitionImpl.SECURITY_KEY_EDIT,
|
||||||
|
ActionCategory.FORM),
|
||||||
|
|
||||||
EXAM_SEB_CLIENT_CONFIG_EXPORT(
|
EXAM_SEB_CLIENT_CONFIG_EXPORT(
|
||||||
new LocTextKey("sebserver.exam.action.createClientToStartExam"),
|
new LocTextKey("sebserver.exam.action.createClientToStartExam"),
|
||||||
ImageIcon.EXPORT,
|
ImageIcon.EXPORT,
|
||||||
|
@ -849,6 +860,11 @@ public enum ActionDefinition {
|
||||||
ImageIcon.YES,
|
ImageIcon.YES,
|
||||||
PageStateDefinitionImpl.MONITORING_CLIENT_CONNECTION,
|
PageStateDefinitionImpl.MONITORING_CLIENT_CONNECTION,
|
||||||
ActionCategory.EXAM_MONITORING_NOTIFICATION_LIST),
|
ActionCategory.EXAM_MONITORING_NOTIFICATION_LIST),
|
||||||
|
MONITOR_EXAM_CLIENT_CONNECTION_GRANT_SIGNATURE_KEY(
|
||||||
|
new LocTextKey("sebserver.monitoring.exam.connection.action.grant.signaturekey"),
|
||||||
|
ImageIcon.VERIFY,
|
||||||
|
PageStateDefinitionImpl.MONITORING_CLIENT_CONNECTION,
|
||||||
|
ActionCategory.FORM),
|
||||||
|
|
||||||
MONITOR_EXAM_QUIT_SELECTED(
|
MONITOR_EXAM_QUIT_SELECTED(
|
||||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.selected"),
|
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.selected"),
|
||||||
|
|
|
@ -28,6 +28,7 @@ import ch.ethz.seb.sebserver.gui.content.exam.ClientGroupForm;
|
||||||
import ch.ethz.seb.sebserver.gui.content.exam.ClientGroupTemplateForm;
|
import ch.ethz.seb.sebserver.gui.content.exam.ClientGroupTemplateForm;
|
||||||
import ch.ethz.seb.sebserver.gui.content.exam.ExamForm;
|
import ch.ethz.seb.sebserver.gui.content.exam.ExamForm;
|
||||||
import ch.ethz.seb.sebserver.gui.content.exam.ExamList;
|
import ch.ethz.seb.sebserver.gui.content.exam.ExamList;
|
||||||
|
import ch.ethz.seb.sebserver.gui.content.exam.ExamSignatureKeyForm;
|
||||||
import ch.ethz.seb.sebserver.gui.content.exam.ExamTemplateForm;
|
import ch.ethz.seb.sebserver.gui.content.exam.ExamTemplateForm;
|
||||||
import ch.ethz.seb.sebserver.gui.content.exam.ExamTemplateList;
|
import ch.ethz.seb.sebserver.gui.content.exam.ExamTemplateList;
|
||||||
import ch.ethz.seb.sebserver.gui.content.exam.IndicatorForm;
|
import ch.ethz.seb.sebserver.gui.content.exam.IndicatorForm;
|
||||||
|
@ -68,6 +69,7 @@ public enum PageStateDefinitionImpl implements PageStateDefinition {
|
||||||
EXAM_EDIT(Type.FORM_EDIT, ExamForm.class, ActivityDefinition.EXAM),
|
EXAM_EDIT(Type.FORM_EDIT, ExamForm.class, ActivityDefinition.EXAM),
|
||||||
INDICATOR_EDIT(Type.FORM_EDIT, IndicatorForm.class, ActivityDefinition.EXAM),
|
INDICATOR_EDIT(Type.FORM_EDIT, IndicatorForm.class, ActivityDefinition.EXAM),
|
||||||
CLIENT_GROUP_EDIT(Type.FORM_EDIT, ClientGroupForm.class, ActivityDefinition.EXAM),
|
CLIENT_GROUP_EDIT(Type.FORM_EDIT, ClientGroupForm.class, ActivityDefinition.EXAM),
|
||||||
|
SECURITY_KEY_EDIT(Type.FORM_EDIT, ExamSignatureKeyForm.class, ActivityDefinition.EXAM),
|
||||||
|
|
||||||
EXAM_TEMPLATE_LIST(Type.LIST_VIEW, ExamTemplateList.class, ActivityDefinition.EXAM_TEMPLATE),
|
EXAM_TEMPLATE_LIST(Type.LIST_VIEW, ExamTemplateList.class, ActivityDefinition.EXAM_TEMPLATE),
|
||||||
EXAM_TEMPLATE_VIEW(Type.LIST_VIEW, ExamTemplateForm.class, ActivityDefinition.EXAM_TEMPLATE),
|
EXAM_TEMPLATE_VIEW(Type.LIST_VIEW, ExamTemplateForm.class, ActivityDefinition.EXAM_TEMPLATE),
|
||||||
|
|
|
@ -252,6 +252,8 @@ public class ExamForm implements TemplateComposer {
|
||||||
final ExamStatus examStatus = exam.getStatus();
|
final ExamStatus examStatus = exam.getStatus();
|
||||||
final boolean editable = modifyGrant &&
|
final boolean editable = modifyGrant &&
|
||||||
(examStatus == ExamStatus.UP_COMING || examStatus == ExamStatus.RUNNING);
|
(examStatus == ExamStatus.UP_COMING || examStatus == ExamStatus.RUNNING);
|
||||||
|
final boolean signatureKeyCheckEnabled = BooleanUtils.toBoolean(
|
||||||
|
exam.additionalAttributes.get(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED));
|
||||||
|
|
||||||
final boolean sebRestrictionAvailable = testSEBRestrictionAPI(exam);
|
final boolean sebRestrictionAvailable = testSEBRestrictionAPI(exam);
|
||||||
final boolean isRestricted = readonly && sebRestrictionAvailable && this.restService
|
final boolean isRestricted = readonly && sebRestrictionAvailable && this.restService
|
||||||
|
@ -465,6 +467,14 @@ public class ExamForm implements TemplateComposer {
|
||||||
.publishIf(() -> sebRestrictionAvailable && readonly && modifyGrant && !importFromQuizData
|
.publishIf(() -> sebRestrictionAvailable && readonly && modifyGrant && !importFromQuizData
|
||||||
&& BooleanUtils.isTrue(isRestricted))
|
&& BooleanUtils.isTrue(isRestricted))
|
||||||
|
|
||||||
|
.newAction(ActionDefinition.EXAM_SECURITY_KEY_ENABLED)
|
||||||
|
.withEntityKey(entityKey)
|
||||||
|
.publishIf(() -> signatureKeyCheckEnabled && readonly)
|
||||||
|
|
||||||
|
.newAction(ActionDefinition.EXAM_SECURITY_KEY_DISABLED)
|
||||||
|
.withEntityKey(entityKey)
|
||||||
|
.publishIf(() -> !signatureKeyCheckEnabled && readonly)
|
||||||
|
|
||||||
.newAction(ActionDefinition.EXAM_PROCTORING_ON)
|
.newAction(ActionDefinition.EXAM_PROCTORING_ON)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
.withExec(this.proctoringSettingsPopup.settingsFunction(this.pageService, modifyGrant && editable))
|
.withExec(this.proctoringSettingsPopup.settingsFunction(this.pageService, modifyGrant && editable))
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* 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.EntityKey;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||||
|
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.TemplateComposer;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||||
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Component
|
||||||
|
@GuiProfile
|
||||||
|
public class ExamSignatureKeyForm implements TemplateComposer {
|
||||||
|
|
||||||
|
private static final LocTextKey TILE =
|
||||||
|
new LocTextKey("sebserver.exam.signaturekey.title");
|
||||||
|
|
||||||
|
private static final LocTextKey FORM_ENABLED =
|
||||||
|
new LocTextKey("sebserver.exam.signaturekey.form.enabled");
|
||||||
|
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_LIST_TITLE =
|
||||||
|
new LocTextKey("sebserver.exam.signaturekey.keylist.title");
|
||||||
|
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 final PageService pageService;
|
||||||
|
private final ResourceService resourceService;
|
||||||
|
private final I18nSupport i18nSupport;
|
||||||
|
|
||||||
|
public ExamSignatureKeyForm(
|
||||||
|
final PageService pageService,
|
||||||
|
final ResourceService resourceService,
|
||||||
|
final I18nSupport i18nSupport) {
|
||||||
|
|
||||||
|
this.pageService = pageService;
|
||||||
|
this.resourceService = resourceService;
|
||||||
|
this.i18nSupport = i18nSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void compose(final PageContext pageContext) {
|
||||||
|
final RestService restService = this.resourceService.getRestService();
|
||||||
|
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||||
|
final EntityKey entityKey = pageContext.getEntityKey();
|
||||||
|
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
|
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature;
|
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||||
|
@ -61,6 +62,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.indicator.Ge
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.ConfirmPendingClientNotification;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.ConfirmPendingClientNotification;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData;
|
||||||
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionSecurityKey;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetPendingClientNotifications;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetPendingClientNotifications;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails;
|
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails;
|
||||||
|
@ -124,6 +126,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
private final InstructionProcessor instructionProcessor;
|
private final InstructionProcessor instructionProcessor;
|
||||||
private final SEBClientEventDetailsPopup sebClientLogDetailsPopup;
|
private final SEBClientEventDetailsPopup sebClientLogDetailsPopup;
|
||||||
private final SEBSendLockPopup sebSendLockPopup;
|
private final SEBSendLockPopup sebSendLockPopup;
|
||||||
|
private final SignatureKeyGrantPopup signatureKeyGrantPopup;
|
||||||
private final MonitoringProctoringService monitoringProctoringService;
|
private final MonitoringProctoringService monitoringProctoringService;
|
||||||
private final long pollInterval;
|
private final long pollInterval;
|
||||||
private final int pageSize;
|
private final int pageSize;
|
||||||
|
@ -139,6 +142,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
final SEBClientEventDetailsPopup sebClientLogDetailsPopup,
|
final SEBClientEventDetailsPopup sebClientLogDetailsPopup,
|
||||||
final MonitoringProctoringService monitoringProctoringService,
|
final MonitoringProctoringService monitoringProctoringService,
|
||||||
final SEBSendLockPopup sebSendLockPopup,
|
final SEBSendLockPopup sebSendLockPopup,
|
||||||
|
final SignatureKeyGrantPopup signatureKeyGrantPopup,
|
||||||
@Value("${sebserver.gui.webservice.poll-interval:500}") final long pollInterval,
|
@Value("${sebserver.gui.webservice.poll-interval:500}") final long pollInterval,
|
||||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||||
|
|
||||||
|
@ -151,6 +155,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
this.pollInterval = pollInterval;
|
this.pollInterval = pollInterval;
|
||||||
this.sebClientLogDetailsPopup = sebClientLogDetailsPopup;
|
this.sebClientLogDetailsPopup = sebClientLogDetailsPopup;
|
||||||
this.sebSendLockPopup = sebSendLockPopup;
|
this.sebSendLockPopup = sebSendLockPopup;
|
||||||
|
this.signatureKeyGrantPopup = signatureKeyGrantPopup;
|
||||||
this.pageSize = pageSize;
|
this.pageSize = pageSize;
|
||||||
|
|
||||||
this.typeFilter = new TableFilterAttribute(
|
this.typeFilter = new TableFilterAttribute(
|
||||||
|
@ -395,6 +400,26 @@ public class MonitoringClientConnection implements TemplateComposer {
|
||||||
.publishIf(() -> isExamSupporter.getAsBoolean() &&
|
.publishIf(() -> isExamSupporter.getAsBoolean() &&
|
||||||
connectionData.clientConnection.status.clientActiveStatus);
|
connectionData.clientConnection.status.clientActiveStatus);
|
||||||
|
|
||||||
|
if (clientConnectionDetails.checkSecurityGrant) {
|
||||||
|
final SecurityKey securityKey = this.pageService
|
||||||
|
.getRestService()
|
||||||
|
.getBuilder(GetClientConnectionSecurityKey.class)
|
||||||
|
.withURIVariable(API.PARAM_PARENT_MODEL_ID, parentEntityKey.modelId)
|
||||||
|
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||||
|
.call()
|
||||||
|
.getOrThrow();
|
||||||
|
|
||||||
|
if (securityKey.id < 0) {
|
||||||
|
actionBuilder
|
||||||
|
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_GRANT_SIGNATURE_KEY)
|
||||||
|
.withParentEntityKey(parentEntityKey)
|
||||||
|
.withEntityKey(entityKey)
|
||||||
|
.withExec(action -> this.signatureKeyGrantPopup.showGrantPopup(action, securityKey))
|
||||||
|
.noEventPropagation()
|
||||||
|
.publishIf(isExamSupporter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (connectionData.clientConnection.status == ConnectionStatus.ACTIVE) {
|
if (connectionData.clientConnection.status == ConnectionStatus.ACTIVE) {
|
||||||
final ProctoringServiceSettings proctoringSettings = restService
|
final ProctoringServiceSettings proctoringSettings = restService
|
||||||
.getBuilder(GetExamProctoringSettings.class)
|
.getBuilder(GetExamProctoringSettings.class)
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* 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.monitoring;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.eclipse.swt.widgets.Composite;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||||
|
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.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.session.GrantClientConnectionSecurityKey;
|
||||||
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Component
|
||||||
|
@GuiProfile
|
||||||
|
public class SignatureKeyGrantPopup {
|
||||||
|
|
||||||
|
private static final LocTextKey TITLE_TEXT_KEY =
|
||||||
|
new LocTextKey("sebserver.monitoring.signaturegrant.title");
|
||||||
|
private static final LocTextKey TITLE_TEXT_INFO =
|
||||||
|
new LocTextKey("sebserver.monitoring.signaturegrant.info");
|
||||||
|
|
||||||
|
private static final LocTextKey TITLE_TEXT_FORM_SIGNATURE =
|
||||||
|
new LocTextKey("sebserver.monitoring.signaturegrant.signature");
|
||||||
|
private static final LocTextKey TITLE_TEXT_FORM_TAG =
|
||||||
|
new LocTextKey("sebserver.monitoring.signaturegrant.tag");
|
||||||
|
|
||||||
|
private final PageService pageService;
|
||||||
|
|
||||||
|
protected SignatureKeyGrantPopup(final PageService pageService) {
|
||||||
|
this.pageService = pageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PageAction showGrantPopup(final PageAction action, final SecurityKey securityKey) {
|
||||||
|
final PageContext pageContext = action.pageContext();
|
||||||
|
final PopupComposer popupComposer = new PopupComposer(this.pageService, pageContext, securityKey);
|
||||||
|
try {
|
||||||
|
final ModalInputDialog<FormHandle<?>> dialog =
|
||||||
|
new ModalInputDialog<>(
|
||||||
|
action.pageContext().getParent().getShell(),
|
||||||
|
this.pageService.getWidgetFactory());
|
||||||
|
dialog.setDialogWidth(700);
|
||||||
|
|
||||||
|
final Predicate<FormHandle<?>> applyGrant = formHandle -> applyGrant(
|
||||||
|
pageContext,
|
||||||
|
formHandle);
|
||||||
|
|
||||||
|
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 SecurityKey securityKey;
|
||||||
|
|
||||||
|
protected PopupComposer(
|
||||||
|
final PageService pageService,
|
||||||
|
final PageContext pageContext,
|
||||||
|
final SecurityKey securityKey) {
|
||||||
|
|
||||||
|
this.pageService = pageService;
|
||||||
|
this.pageContext = pageContext;
|
||||||
|
this.securityKey = securityKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Supplier<FormHandle<?>> compose(final Composite parent) {
|
||||||
|
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||||
|
widgetFactory.addFormSubContextHeader(parent, TITLE_TEXT_INFO, null);
|
||||||
|
|
||||||
|
//final Composite defaultPageLayout = widgetFactory.defaultPageLayout(parent, TITLE_TEXT_INFO);
|
||||||
|
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))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return () -> form;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean applyGrant(
|
||||||
|
final PageContext pageContext,
|
||||||
|
final FormHandle<?> formHandle) {
|
||||||
|
|
||||||
|
final EntityKey examKey = pageContext.getParentEntityKey();
|
||||||
|
final EntityKey connectionKey = pageContext.getEntityKey();
|
||||||
|
|
||||||
|
return this.pageService
|
||||||
|
.getRestService()
|
||||||
|
.getBuilder(GrantClientConnectionSecurityKey.class)
|
||||||
|
.withURIVariable(API.PARAM_PARENT_MODEL_ID, examKey.modelId)
|
||||||
|
.withURIVariable(API.PARAM_MODEL_ID, connectionKey.modelId)
|
||||||
|
.withFormBinding(formHandle.getFormBinding())
|
||||||
|
.call()
|
||||||
|
.onError(formHandle::handleError)
|
||||||
|
.hasValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -60,7 +60,6 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig.VDIType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
||||||
|
@ -87,7 +86,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.Ge
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetViews;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetViews;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccountNames;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.useraccount.GetUserAccountNames;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable.MonitoringEntry;
|
import ch.ethz.seb.sebserver.gui.service.session.MonitoringEntry;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Service
|
@Service
|
||||||
|
@ -626,27 +625,38 @@ public class ResourceService {
|
||||||
.getText(ResourceService.EXAMCONFIG_STATUS_PREFIX + config.configStatus.name());
|
.getText(ResourceService.EXAMCONFIG_STATUS_PREFIX + config.configStatus.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Function<ClientConnectionData, String> localizedClientConnectionStatusNameFunction() {
|
// public Function<ClientConnectionData, String> localizedClientConnectionStatusNameFunction() {
|
||||||
|
//
|
||||||
// Memoizing
|
// // Memoizing
|
||||||
final String missing = this.i18nSupport.getText(
|
// final String missing = this.i18nSupport.getText(
|
||||||
SEB_CONNECTION_STATUS_KEY_PREFIX + MISSING_CLIENT_PING_NAME_KEY,
|
// SEB_CONNECTION_STATUS_KEY_PREFIX + MISSING_CLIENT_PING_NAME_KEY,
|
||||||
MISSING_CLIENT_PING_NAME_KEY);
|
// MISSING_CLIENT_PING_NAME_KEY);
|
||||||
final EnumMap<ConnectionStatus, String> localizedNames = new EnumMap<>(ConnectionStatus.class);
|
// final String missingGrant = this.i18nSupport.getText(
|
||||||
Arrays.asList(ConnectionStatus.values()).stream().forEach(state -> localizedNames.put(state, this.i18nSupport
|
// SEB_CONNECTION_STATUS_KEY_PREFIX + MISSING_CLIENT_SEC_GRANT_NAME_KEY,
|
||||||
.getText(SEB_CONNECTION_STATUS_KEY_PREFIX + state.name(), state.name())));
|
// MISSING_CLIENT_SEC_GRANT_NAME_KEY);
|
||||||
|
// final EnumMap<ConnectionStatus, String> localizedNames = new EnumMap<>(ConnectionStatus.class);
|
||||||
return connectionData -> {
|
// Arrays.asList(ConnectionStatus.values()).stream().forEach(state -> localizedNames.put(state, this.i18nSupport
|
||||||
if (connectionData == null) {
|
// .getText(SEB_CONNECTION_STATUS_KEY_PREFIX + state.name(), state.name())));
|
||||||
localizedNames.get(ConnectionStatus.UNDEFINED);
|
//
|
||||||
}
|
// return connectionData -> {
|
||||||
if (connectionData.missingPing && connectionData.clientConnection.status.establishedStatus) {
|
// if (connectionData == null) {
|
||||||
return missing;
|
// localizedNames.get(ConnectionStatus.UNDEFINED);
|
||||||
} else {
|
// }
|
||||||
return localizedNames.get(connectionData.clientConnection.status);
|
// if (connectionData.clientConnection.status.establishedStatus) {
|
||||||
}
|
// if (connectionData.c !connectionData.clientConnection.securityCheckGranted) {
|
||||||
};
|
// return missingGrant;
|
||||||
}
|
// }
|
||||||
|
// if (connectionData.missingPing) {
|
||||||
|
// return missing;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if (connectionData.missingPing && connectionData.clientConnection.status.establishedStatus) {
|
||||||
|
// return missing;
|
||||||
|
// } else {
|
||||||
|
// return localizedNames.get(connectionData.clientConnection.status);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
public Function<MonitoringEntry, String> localizedClientMonitoringStatusNameFunction() {
|
public Function<MonitoringEntry, String> localizedClientMonitoringStatusNameFunction() {
|
||||||
|
|
||||||
|
|
|
@ -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.session;
|
||||||
|
|
||||||
|
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 GetClientConnectionSecurityKey extends RestCall<SecurityKey> {
|
||||||
|
|
||||||
|
public GetClientConnectionSecurityKey() {
|
||||||
|
super(new TypeKey<>(
|
||||||
|
CallType.GET_SINGLE,
|
||||||
|
EntityType.SEB_SECURITY_KEY_REGISTRY,
|
||||||
|
new TypeReference<SecurityKey>() {
|
||||||
|
}),
|
||||||
|
HttpMethod.GET,
|
||||||
|
MediaType.APPLICATION_FORM_URLENCODED,
|
||||||
|
API.EXAM_MONITORING_ENDPOINT +
|
||||||
|
API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
|
||||||
|
API.EXAM_MONITORING_SIGNATURE_KEY_ENDPOINT +
|
||||||
|
API.MODEL_ID_VAR_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.session;
|
||||||
|
|
||||||
|
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 GrantClientConnectionSecurityKey extends RestCall<SecurityKey> {
|
||||||
|
|
||||||
|
public GrantClientConnectionSecurityKey() {
|
||||||
|
super(new TypeKey<>(
|
||||||
|
CallType.GET_SINGLE,
|
||||||
|
EntityType.SEB_SECURITY_KEY_REGISTRY,
|
||||||
|
new TypeReference<SecurityKey>() {
|
||||||
|
}),
|
||||||
|
HttpMethod.POST,
|
||||||
|
MediaType.APPLICATION_FORM_URLENCODED,
|
||||||
|
API.EXAM_MONITORING_ENDPOINT +
|
||||||
|
API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
|
||||||
|
API.EXAM_MONITORING_SIGNATURE_KEY_ENDPOINT +
|
||||||
|
API.MODEL_ID_VAR_PATH_SEGMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
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.model.session.ClientConnectionData;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
|
||||||
import ch.ethz.seb.sebserver.gbl.monitoring.IndicatorValue;
|
import ch.ethz.seb.sebserver.gbl.monitoring.IndicatorValue;
|
||||||
|
@ -47,7 +48,7 @@ import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor;
|
||||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||||
|
|
||||||
public class ClientConnectionDetails {
|
public class ClientConnectionDetails implements MonitoringEntry {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ClientConnectionDetails.class);
|
private static final Logger log = LoggerFactory.getLogger(ClientConnectionDetails.class);
|
||||||
|
|
||||||
|
@ -72,7 +73,8 @@ public class ClientConnectionDetails {
|
||||||
private final RestCall<ClientConnectionData>.RestCallBuilder restCallBuilder;
|
private final RestCall<ClientConnectionData>.RestCallBuilder restCallBuilder;
|
||||||
private final FormHandle<?> formHandle;
|
private final FormHandle<?> formHandle;
|
||||||
private final ColorData colorData;
|
private final ColorData colorData;
|
||||||
private final Function<ClientConnectionData, String> localizedClientConnectionStatusNameFunction;
|
private final Function<MonitoringEntry, String> localizedClientConnectionStatusNameFunction;
|
||||||
|
public final boolean checkSecurityGrant;
|
||||||
|
|
||||||
private ClientConnectionData connectionData = null;
|
private ClientConnectionData connectionData = null;
|
||||||
private boolean statusChanged = true;
|
private boolean statusChanged = true;
|
||||||
|
@ -94,6 +96,8 @@ public class ClientConnectionDetails {
|
||||||
this.resourceService = pageService.getResourceService();
|
this.resourceService = pageService.getResourceService();
|
||||||
this.restCallBuilder = restCallBuilder;
|
this.restCallBuilder = restCallBuilder;
|
||||||
this.colorData = new ColorData(display);
|
this.colorData = new ColorData(display);
|
||||||
|
this.checkSecurityGrant = BooleanUtils.toBoolean(
|
||||||
|
exam.additionalAttributes.get(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED));
|
||||||
this.indicatorMapping = IndicatorData.createFormIndicators(
|
this.indicatorMapping = IndicatorData.createFormIndicators(
|
||||||
indicators,
|
indicators,
|
||||||
display,
|
display,
|
||||||
|
@ -146,7 +150,26 @@ public class ClientConnectionDetails {
|
||||||
|
|
||||||
this.formHandle = formBuilder.build();
|
this.formHandle = formBuilder.build();
|
||||||
this.localizedClientConnectionStatusNameFunction =
|
this.localizedClientConnectionStatusNameFunction =
|
||||||
this.resourceService.localizedClientConnectionStatusNameFunction();
|
this.resourceService.localizedClientMonitoringStatusNameFunction();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConnectionStatus getStatus() {
|
||||||
|
if (this.connectionData == null) {
|
||||||
|
return ConnectionStatus.UNDEFINED;
|
||||||
|
}
|
||||||
|
return this.connectionData.clientConnection.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasMissingPing() {
|
||||||
|
return (this.connectionData != null) ? this.connectionData.missingPing : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasMissingGrant() {
|
||||||
|
return (this.connectionData != null)
|
||||||
|
? this.checkSecurityGrant && !this.connectionData.clientConnection.securityCheckGranted : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStatusChangeListener(final Consumer<ClientConnectionData> statusChangeListener) {
|
public void setStatusChangeListener(final Consumer<ClientConnectionData> statusChangeListener) {
|
||||||
|
@ -210,8 +233,8 @@ public class ClientConnectionDetails {
|
||||||
// update status
|
// update status
|
||||||
form.setFieldValue(
|
form.setFieldValue(
|
||||||
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
||||||
this.localizedClientConnectionStatusNameFunction.apply(this.connectionData));
|
this.localizedClientConnectionStatusNameFunction.apply(this));
|
||||||
final Color statusColor = this.colorData.getStatusColor(this.connectionData);
|
final Color statusColor = this.colorData.getStatusColor(this);
|
||||||
final Color statusTextColor = this.colorData.getStatusTextColor(statusColor);
|
final Color statusTextColor = this.colorData.getStatusTextColor(statusColor);
|
||||||
form.setFieldColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusColor);
|
form.setFieldColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusColor);
|
||||||
form.setFieldTextColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusTextColor);
|
form.setFieldTextColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusTextColor);
|
||||||
|
|
|
@ -449,14 +449,6 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
|
||||||
// TODO if right click get selected item and show additional information (notification)
|
// TODO if right click get selected item and show additional information (notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface MonitoringEntry {
|
|
||||||
ConnectionStatus getStatus();
|
|
||||||
|
|
||||||
boolean hasMissingPing();
|
|
||||||
|
|
||||||
boolean hasMissingGrant();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class UpdatableTableItem implements Comparable<UpdatableTableItem>, MonitoringEntry {
|
private final class UpdatableTableItem implements Comparable<UpdatableTableItem>, MonitoringEntry {
|
||||||
|
|
||||||
final Long connectionId;
|
final Long connectionId;
|
||||||
|
|
|
@ -16,7 +16,6 @@ import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable.MonitoringEntry;
|
|
||||||
|
|
||||||
public class ColorData {
|
public class ColorData {
|
||||||
|
|
||||||
|
@ -36,19 +35,6 @@ public class ColorData {
|
||||||
this.lightColor = new Color(display, Constants.WHITE_RGB);
|
this.lightColor = new Color(display, Constants.WHITE_RGB);
|
||||||
}
|
}
|
||||||
|
|
||||||
Color getStatusColor(final ClientConnectionData connectionData) {
|
|
||||||
if (connectionData == null || connectionData.clientConnection == null) {
|
|
||||||
return this.defaultColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (connectionData.clientConnection.status) {
|
|
||||||
case ACTIVE:
|
|
||||||
return (connectionData.missingPing) ? this.color2 : this.color1;
|
|
||||||
default:
|
|
||||||
return this.defaultColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Color getStatusColor(final MonitoringEntry entry) {
|
Color getStatusColor(final MonitoringEntry entry) {
|
||||||
final ConnectionStatus status = entry.getStatus();
|
final ConnectionStatus status = entry.getStatus();
|
||||||
if (status == null) {
|
if (status == null) {
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* 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.session;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||||
|
|
||||||
|
public interface MonitoringEntry {
|
||||||
|
|
||||||
|
ConnectionStatus getStatus();
|
||||||
|
|
||||||
|
boolean hasMissingPing();
|
||||||
|
|
||||||
|
boolean hasMissingGrant();
|
||||||
|
}
|
|
@ -144,7 +144,10 @@ public class WidgetFactory {
|
||||||
RESTRICTION("restriction.png"),
|
RESTRICTION("restriction.png"),
|
||||||
VISIBILITY("visibility.png"),
|
VISIBILITY("visibility.png"),
|
||||||
VISIBILITY_OFF("visibility_off.png"),
|
VISIBILITY_OFF("visibility_off.png"),
|
||||||
NOTIFICATION("notification.png");
|
NOTIFICATION("notification.png"),
|
||||||
|
VERIFY("verify.png"),
|
||||||
|
SHIELD("shield.png"),
|
||||||
|
NO_SHIELD("no_shield.png");
|
||||||
|
|
||||||
public String fileName;
|
public String fileName;
|
||||||
private ImageData image = null;
|
private ImageData image = null;
|
||||||
|
@ -466,6 +469,7 @@ public class WidgetFactory {
|
||||||
final LocTextKey locToolTextKey) {
|
final LocTextKey locToolTextKey) {
|
||||||
|
|
||||||
final Label label = new Label(parent, SWT.NONE);
|
final Label label = new Label(parent, SWT.NONE);
|
||||||
|
label.setData(RWT.MARKUP_ENABLED, true);
|
||||||
this.polyglotPageService.injectI18n(label, locTextKey, locToolTextKey);
|
this.polyglotPageService.injectI18n(label, locTextKey, locToolTextKey);
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
@ -477,6 +481,7 @@ public class WidgetFactory {
|
||||||
final LocTextKey locToolTextKey) {
|
final LocTextKey locToolTextKey) {
|
||||||
|
|
||||||
final Label label = new Label(parent, SWT.NONE);
|
final Label label = new Label(parent, SWT.NONE);
|
||||||
|
label.setData(RWT.MARKUP_ENABLED, true);
|
||||||
this.polyglotPageService.injectI18n(label, locTextKey, locToolTextKey);
|
this.polyglotPageService.injectI18n(label, locTextKey, locToolTextKey);
|
||||||
label.setData(RWT.CUSTOM_VARIANT, variant.key);
|
label.setData(RWT.CUSTOM_VARIANT, variant.key);
|
||||||
return label;
|
return label;
|
||||||
|
@ -484,6 +489,7 @@ public class WidgetFactory {
|
||||||
|
|
||||||
public Label labelLocalizedTitle(final Composite content, final LocTextKey locTextKey) {
|
public Label labelLocalizedTitle(final Composite content, final LocTextKey locTextKey) {
|
||||||
final Label labelLocalized = labelLocalized(content, CustomVariant.TEXT_H1, locTextKey);
|
final Label labelLocalized = labelLocalized(content, CustomVariant.TEXT_H1, locTextKey);
|
||||||
|
labelLocalized.setData(RWT.MARKUP_ENABLED, true);
|
||||||
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
|
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
|
||||||
labelLocalized.setLayoutData(gridData);
|
labelLocalized.setLayoutData(gridData);
|
||||||
return labelLocalized;
|
return labelLocalized;
|
||||||
|
|
|
@ -167,6 +167,12 @@ public interface ClientConnectionDAO extends
|
||||||
*
|
*
|
||||||
* @param examId the exam identifier
|
* @param examId the exam identifier
|
||||||
* @return Result refer to a collection of client connection records or to an error when happened */
|
* @return Result refer to a collection of client connection records or to an error when happened */
|
||||||
Result<Collection<ClientConnectionRecord>> getAllConnectionIdsForExam(Long examId);
|
Result<Collection<ClientConnectionRecord>> getAllConnectionRecordsForExam(Long examId);
|
||||||
|
|
||||||
|
/** Get all client connection identifiers for an exam.
|
||||||
|
*
|
||||||
|
* @param examId the exam identifier
|
||||||
|
* @return Result refer to a collection of client connection identifiers or to an error when happened */
|
||||||
|
Result<Collection<Long>> getAllConnectionIdsForExam(Long examId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,4 +36,11 @@ public interface SecurityKeyRegistryDAO extends EntityDAO<SecurityKey, SecurityK
|
||||||
@EventListener(ExamTemplateDeletionEvent.class)
|
@EventListener(ExamTemplateDeletionEvent.class)
|
||||||
void notifyExamTemplateDeletion(ExamTemplateDeletionEvent event);
|
void notifyExamTemplateDeletion(ExamTemplateDeletionEvent event);
|
||||||
|
|
||||||
|
/** This checks if there is already a grant for the given key and return it if available
|
||||||
|
* or the given key otherwise.
|
||||||
|
*
|
||||||
|
* @param key SecurityKey data to check if there is a grant registered
|
||||||
|
* @return Result refer to the grant if available or the the given key if not or to an error when happened */
|
||||||
|
Result<SecurityKey> getGrantOr(final SecurityKey key);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -764,7 +764,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Result<Collection<ClientConnectionRecord>> getAllConnectionIdsForExam(final Long examId) {
|
public Result<Collection<ClientConnectionRecord>> getAllConnectionRecordsForExam(final Long examId) {
|
||||||
return Result.tryCatch(() -> this.clientConnectionRecordMapper
|
return Result.tryCatch(() -> this.clientConnectionRecordMapper
|
||||||
.selectByExample()
|
.selectByExample()
|
||||||
.where(
|
.where(
|
||||||
|
@ -774,6 +774,18 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
.execute());
|
.execute());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<Collection<Long>> getAllConnectionIdsForExam(final Long examId) {
|
||||||
|
return Result.tryCatch(() -> this.clientConnectionRecordMapper
|
||||||
|
.selectIdsByExample()
|
||||||
|
.where(
|
||||||
|
ClientConnectionRecordDynamicSqlSupport.examId,
|
||||||
|
SqlBuilder.isEqualTo(examId))
|
||||||
|
.build()
|
||||||
|
.execute());
|
||||||
|
}
|
||||||
|
|
||||||
private Result<ClientConnectionRecord> recordById(final Long id) {
|
private Result<ClientConnectionRecord> recordById(final Long id) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -30,6 +31,7 @@ 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;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey.KeyType;
|
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey.KeyType;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SecurityKeyRegistryRecordDynamicSqlSupport;
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SecurityKeyRegistryRecordDynamicSqlSupport;
|
||||||
|
@ -46,9 +48,14 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
|
||||||
public class SecurityKeyRegistryDAOImpl implements SecurityKeyRegistryDAO {
|
public class SecurityKeyRegistryDAOImpl implements SecurityKeyRegistryDAO {
|
||||||
|
|
||||||
private final SecurityKeyRegistryRecordMapper securityKeyRegistryRecordMapper;
|
private final SecurityKeyRegistryRecordMapper securityKeyRegistryRecordMapper;
|
||||||
|
private final Cryptor cryptor;
|
||||||
|
|
||||||
|
public SecurityKeyRegistryDAOImpl(
|
||||||
|
final SecurityKeyRegistryRecordMapper securityKeyRegistryRecordMapper,
|
||||||
|
final Cryptor cryptor) {
|
||||||
|
|
||||||
public SecurityKeyRegistryDAOImpl(final SecurityKeyRegistryRecordMapper securityKeyRegistryRecordMapper) {
|
|
||||||
this.securityKeyRegistryRecordMapper = securityKeyRegistryRecordMapper;
|
this.securityKeyRegistryRecordMapper = securityKeyRegistryRecordMapper;
|
||||||
|
this.cryptor = cryptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -123,7 +130,7 @@ public class SecurityKeyRegistryDAOImpl implements SecurityKeyRegistryDAO {
|
||||||
public Result<SecurityKey> createNew(final SecurityKey data) {
|
public Result<SecurityKey> createNew(final SecurityKey data) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
checkUniqueTag(data);
|
checkUniqueKey(data);
|
||||||
|
|
||||||
final SecurityKeyRegistryRecord newRecord = new SecurityKeyRegistryRecord(
|
final SecurityKeyRegistryRecord newRecord = new SecurityKeyRegistryRecord(
|
||||||
null,
|
null,
|
||||||
|
@ -146,8 +153,6 @@ public class SecurityKeyRegistryDAOImpl implements SecurityKeyRegistryDAO {
|
||||||
public Result<SecurityKey> save(final SecurityKey data) {
|
public Result<SecurityKey> save(final SecurityKey data) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
checkUniqueTag(data);
|
|
||||||
|
|
||||||
final SecurityKeyRegistryRecord newRecord = new SecurityKeyRegistryRecord(
|
final SecurityKeyRegistryRecord newRecord = new SecurityKeyRegistryRecord(
|
||||||
null,
|
null,
|
||||||
data.institutionId,
|
data.institutionId,
|
||||||
|
@ -312,6 +317,32 @@ public class SecurityKeyRegistryDAOImpl implements SecurityKeyRegistryDAO {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<SecurityKey> getGrantOr(final SecurityKey key) {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
final CharSequence signature = this.cryptor.decrypt(key.key).get();
|
||||||
|
return this.securityKeyRegistryRecordMapper
|
||||||
|
.selectByExample()
|
||||||
|
.where(
|
||||||
|
SecurityKeyRegistryRecordDynamicSqlSupport.institutionId,
|
||||||
|
SqlBuilder.isEqualTo(key.institutionId))
|
||||||
|
.and(
|
||||||
|
SecurityKeyRegistryRecordDynamicSqlSupport.examId,
|
||||||
|
isEqualToWhenPresent(key.examId))
|
||||||
|
.and(
|
||||||
|
SecurityKeyRegistryRecordDynamicSqlSupport.keyType,
|
||||||
|
isEqualToWhenPresent((key.keyType == null) ? null : key.keyType.name()))
|
||||||
|
.build()
|
||||||
|
.execute()
|
||||||
|
.stream()
|
||||||
|
.filter(other -> Objects.equals(signature, this.cryptor.decrypt(other.getKeyValue()).getOr(null)))
|
||||||
|
.findFirst()
|
||||||
|
.map(this::toDomainModel)
|
||||||
|
.orElse(key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void notifyExamDeletion(final ExamDeletionEvent event) {
|
public void notifyExamDeletion(final ExamDeletionEvent event) {
|
||||||
try {
|
try {
|
||||||
|
@ -367,18 +398,11 @@ public class SecurityKeyRegistryDAOImpl implements SecurityKeyRegistryDAO {
|
||||||
rec.getExamTemplateId());
|
rec.getExamTemplateId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkUniqueTag(final SecurityKey securityKeyRegistry) {
|
private void checkUniqueKey(final SecurityKey key) {
|
||||||
final Long count = this.securityKeyRegistryRecordMapper
|
if (getGrantOr(key).getOr(key) != key) {
|
||||||
.countByExample()
|
|
||||||
.where(SecurityKeyRegistryRecordDynamicSqlSupport.tag, isEqualTo(securityKeyRegistry.tag))
|
|
||||||
.and(SecurityKeyRegistryRecordDynamicSqlSupport.id, isNotEqualToWhenPresent(securityKeyRegistry.id))
|
|
||||||
.build()
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
if (count != null && count > 0) {
|
|
||||||
throw new FieldValidationException(
|
throw new FieldValidationException(
|
||||||
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG,
|
Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG,
|
||||||
"institution:name:tag.notunique");
|
"securityKey:keyValue:alreadyGranted");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.institution;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
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.SecurityCheckResult;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey;
|
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||||
|
@ -21,7 +22,9 @@ public interface SecurityKeyService {
|
||||||
/** This attribute name is used to store the App-Signature-Key given by a SEB Client */
|
/** 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 static final String ADDITIONAL_ATTR_APP_SIGNATURE_KEY = "APP_SIGNATURE_KEY";
|
||||||
|
|
||||||
Result<Collection<SecurityKey>> getPlainGrants(Long institutionId, Long examId);
|
Result<SecurityKey> getSecurityKeyOfConnection(Long institutionId, Long connectionId);
|
||||||
|
|
||||||
|
Result<AppSignatureKeyInfo> getAppSignaturesInfo(Long institutionId, Long examId);
|
||||||
|
|
||||||
Result<Collection<SecurityKey>> getPlainAppSignatureKeyGrants(Long institutionId, Long examId);
|
Result<Collection<SecurityKey>> getPlainAppSignatureKeyGrants(Long institutionId, Long examId);
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,12 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.institution.impl;
|
||||||
|
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
|
@ -24,6 +28,7 @@ import org.springframework.stereotype.Service;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
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.SecurityCheckResult;
|
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;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey.KeyType;
|
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey.KeyType;
|
||||||
|
@ -69,10 +74,35 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Collection<SecurityKey>> getPlainGrants(final Long institutionId, final Long examId) {
|
public Result<SecurityKey> getSecurityKeyOfConnection(final Long institutionId, final Long connectionId) {
|
||||||
return this.securityKeyRegistryDAO
|
return this.clientConnectionDAO.byPK(connectionId)
|
||||||
.getAll(institutionId, examId, null)
|
.map(connection -> new SecurityKey(
|
||||||
.map(this::decryptAll);
|
null,
|
||||||
|
institutionId,
|
||||||
|
KeyType.APP_SIGNATURE_KEY,
|
||||||
|
decryptStoredSignatureForConnection(connection),
|
||||||
|
connection.sebVersion,
|
||||||
|
null, null))
|
||||||
|
.flatMap(this.securityKeyRegistryDAO::getGrantOr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<AppSignatureKeyInfo> getAppSignaturesInfo(final Long institutionId, final Long examId) {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
final Map<String, Set<Long>> keyMapping = new HashMap<>();
|
||||||
|
|
||||||
|
this.clientConnectionDAO
|
||||||
|
.getAllConnectionRecordsForExam(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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -386,6 +416,15 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
||||||
return decryptSignatureWithConnectionToken(cc.connectionToken, signatureKey);
|
return decryptSignatureWithConnectionToken(cc.connectionToken, signatureKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String decryptStoredSignatureForConnection(final Long cId, final String cToken) {
|
||||||
|
final String signatureKey = getSignatureKeyForConnection(cId);
|
||||||
|
if (StringUtils.isBlank(signatureKey)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptSignatureWithConnectionToken(cToken, signatureKey);
|
||||||
|
}
|
||||||
|
|
||||||
private void saveSignatureKeyForConnection(final ClientConnection clientConnection, final String appSignatureKey) {
|
private void saveSignatureKeyForConnection(final ClientConnection clientConnection, final String appSignatureKey) {
|
||||||
this.additionalAttributesDAO
|
this.additionalAttributesDAO
|
||||||
.saveAdditionalAttribute(
|
.saveAdditionalAttribute(
|
||||||
|
|
|
@ -25,7 +25,7 @@ public class MockSEBRestrictionAPI implements SEBRestrictionAPI {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LmsSetupTestResult testCourseRestrictionAPI() {
|
public LmsSetupTestResult testCourseRestrictionAPI() {
|
||||||
return LmsSetupTestResult.ofQuizRestrictionAPIError(LmsType.MOCKUP, "unsupported");
|
return LmsSetupTestResult.ofOkay(LmsType.MOCKUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -50,6 +50,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
|
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
|
import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
|
||||||
|
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;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features;
|
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;
|
||||||
|
@ -190,7 +191,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
// **** SEB Security Key
|
// **** SEB Security Key and App Signature Key
|
||||||
|
|
||||||
@RequestMapping(
|
@RequestMapping(
|
||||||
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT
|
||||||
|
@ -258,6 +259,24 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
.getOrThrow();
|
.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
|
// **** SEB Security Key
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Page;
|
import ch.ethz.seb.sebserver.gbl.model.Page;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.institution.SecurityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction;
|
||||||
|
@ -463,29 +464,50 @@ public class ExamMonitoringController {
|
||||||
|
|
||||||
@RequestMapping(
|
@RequestMapping(
|
||||||
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
|
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
|
||||||
API.EXAM_MONITORING_GRANT_APP_SIGNATURE_KEY_ENDPOINT +
|
API.EXAM_MONITORING_SIGNATURE_KEY_ENDPOINT +
|
||||||
API.MODEL_ID_VAR_PATH_SEGMENT,
|
API.MODEL_ID_VAR_PATH_SEGMENT,
|
||||||
method = RequestMethod.POST,
|
method = RequestMethod.POST,
|
||||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||||
public void grantAppSignatureKey(
|
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public SecurityKey grantAppSignatureKey(
|
||||||
@RequestParam(
|
@RequestParam(
|
||||||
name = API.PARAM_INSTITUTION_ID,
|
name = API.PARAM_INSTITUTION_ID,
|
||||||
required = true,
|
required = true,
|
||||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||||
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
|
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
|
||||||
@PathVariable(name = API.MODEL_ID_VAR_PATH_SEGMENT, required = true) final Long connectionId,
|
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long connectionId,
|
||||||
@RequestParam(
|
@RequestParam(name = Domain.SEB_SECURITY_KEY_REGISTRY.ATTR_TAG, required = true) final String tagName) {
|
||||||
name = Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
|
|
||||||
required = false) final String connectionToken) {
|
|
||||||
|
|
||||||
checkPrivileges(institutionId, examId);
|
checkPrivileges(institutionId, examId);
|
||||||
|
|
||||||
this.securityKeyService
|
return this.securityKeyService
|
||||||
.registerExamAppSignatureKey(institutionId, examId, connectionId, null)
|
.registerExamAppSignatureKey(institutionId, examId, connectionId, tagName)
|
||||||
.onSuccess(key -> this.securityKeyService.updateAppSignatureKeyGrants(examId))
|
.onSuccess(key -> this.securityKeyService.updateAppSignatureKeyGrants(examId))
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequestMapping(
|
||||||
|
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT +
|
||||||
|
API.EXAM_MONITORING_SIGNATURE_KEY_ENDPOINT +
|
||||||
|
API.MODEL_ID_VAR_PATH_SEGMENT,
|
||||||
|
method = RequestMethod.GET,
|
||||||
|
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||||
|
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public SecurityKey getAppSignatureKey(
|
||||||
|
@RequestParam(
|
||||||
|
name = API.PARAM_INSTITUTION_ID,
|
||||||
|
required = true,
|
||||||
|
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||||
|
@PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId,
|
||||||
|
@PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long connectionId) {
|
||||||
|
|
||||||
|
checkPrivileges(institutionId, examId);
|
||||||
|
return this.securityKeyService
|
||||||
|
.getSecurityKeyOfConnection(institutionId, connectionId)
|
||||||
|
.getOrThrow();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private Exam checkPrivileges(final Long institutionId, final Long examId) {
|
private Exam checkPrivileges(final Long institutionId, final Long examId) {
|
||||||
// check overall privilege
|
// check overall privilege
|
||||||
this.authorization.checkRole(
|
this.authorization.checkRole(
|
||||||
|
|
|
@ -794,6 +794,8 @@ sebserver.exam.proctoring.one.close.error=Failed to close the one-to-one room pr
|
||||||
sebserver.exam.proctoring.collecting.open.error=Failed to open the collecting room.
|
sebserver.exam.proctoring.collecting.open.error=Failed to open the collecting room.
|
||||||
sebserver.exam.proctoring.collecting.close.error=Failed to close the collecting room properly.
|
sebserver.exam.proctoring.collecting.close.error=Failed to close the collecting room properly.
|
||||||
|
|
||||||
|
sebserver.exam.signaturekey.action.edit=App Signature Key
|
||||||
|
|
||||||
|
|
||||||
################################
|
################################
|
||||||
# Connection Configuration
|
# Connection Configuration
|
||||||
|
@ -1984,6 +1986,7 @@ sebserver.monitoring.exam.connection.actions.group3=
|
||||||
sebserver.monitoring.exam.connection.notificationlist.actions=
|
sebserver.monitoring.exam.connection.notificationlist.actions=
|
||||||
sebserver.monitoring.exam.connection.action.confirm.notification=Confirm Notification
|
sebserver.monitoring.exam.connection.action.confirm.notification=Confirm Notification
|
||||||
sebserver.monitoring.exam.connection.action.confirm.notification.text=Are you sure you want to confirm this pending notification?<br/><br/>Note that this will send a notification confirmation instruction to the SEB client and remove this notification from the pending list.
|
sebserver.monitoring.exam.connection.action.confirm.notification.text=Are you sure you want to confirm this pending notification?<br/><br/>Note that this will send a notification confirmation instruction to the SEB client and remove this notification from the pending list.
|
||||||
|
sebserver.monitoring.exam.connection.action.grant.signaturekey=Grant App Signature Key
|
||||||
|
|
||||||
sebserver.monitoring.exam.connection.notificationlist.pleaseSelect=At first please select a notification form the Pending Notification list
|
sebserver.monitoring.exam.connection.notificationlist.pleaseSelect=At first please select a notification form the Pending Notification list
|
||||||
sebserver.monitoring.exam.connection.notificationlist.title=Pending Notification
|
sebserver.monitoring.exam.connection.notificationlist.title=Pending Notification
|
||||||
|
@ -2037,6 +2040,11 @@ sebserver.monitoring.lock.list.name=SEB User Session Identifier
|
||||||
sebserver.monitoring.lock.list.info=SEB Connection Info
|
sebserver.monitoring.lock.list.info=SEB Connection Info
|
||||||
sebserver.monitoring.lock.noselection=Please select at least one active SEB client connection.
|
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.tag=Tag
|
||||||
|
|
||||||
################################
|
################################
|
||||||
# Finished Exams
|
# Finished Exams
|
||||||
################################
|
################################
|
||||||
|
|
BIN
src/main/resources/static/images/no_shield.png
Normal file
BIN
src/main/resources/static/images/no_shield.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 317 B |
BIN
src/main/resources/static/images/shield.png
Normal file
BIN
src/main/resources/static/images/shield.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 285 B |
BIN
src/main/resources/static/images/verify.png
Normal file
BIN
src/main/resources/static/images/verify.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 333 B |
Loading…
Reference in a new issue