SEBSERV-287 back-end implementation

This commit is contained in:
anhefti 2022-04-13 14:21:27 +02:00
parent 3d94637300
commit 191f8432de
22 changed files with 907 additions and 273 deletions

View file

@ -65,8 +65,8 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamCon
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckSEBRestriction; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckSEBRestriction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetDefaultExamTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetDefaultExamTemplate;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplate;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData;
@ -143,7 +143,7 @@ public class ExamForm implements TemplateComposer {
private final PageService pageService; private final PageService pageService;
private final ResourceService resourceService; private final ResourceService resourceService;
private final ExamSEBRestrictionSettings examSEBRestrictionSettings; private final ExamSEBRestrictionSettings examSEBRestrictionSettings;
private final ExamProctoringSettings examProctoringSettings; private final ProctoringSettingsPopup proctoringSettingsPopup;
private final WidgetFactory widgetFactory; private final WidgetFactory widgetFactory;
private final RestService restService; private final RestService restService;
private final ExamDeletePopup examDeletePopup; private final ExamDeletePopup examDeletePopup;
@ -154,7 +154,7 @@ public class ExamForm implements TemplateComposer {
protected ExamForm( protected ExamForm(
final PageService pageService, final PageService pageService,
final ExamSEBRestrictionSettings examSEBRestrictionSettings, final ExamSEBRestrictionSettings examSEBRestrictionSettings,
final ExamProctoringSettings examProctoringSettings, final ProctoringSettingsPopup proctoringSettingsPopup,
final ExamToConfigBindingForm examToConfigBindingForm, final ExamToConfigBindingForm examToConfigBindingForm,
final DownloadService downloadService, final DownloadService downloadService,
final ExamDeletePopup examDeletePopup, final ExamDeletePopup examDeletePopup,
@ -165,7 +165,7 @@ public class ExamForm implements TemplateComposer {
this.pageService = pageService; this.pageService = pageService;
this.resourceService = pageService.getResourceService(); this.resourceService = pageService.getResourceService();
this.examSEBRestrictionSettings = examSEBRestrictionSettings; this.examSEBRestrictionSettings = examSEBRestrictionSettings;
this.examProctoringSettings = examProctoringSettings; this.proctoringSettingsPopup = proctoringSettingsPopup;
this.widgetFactory = pageService.getWidgetFactory(); this.widgetFactory = pageService.getWidgetFactory();
this.restService = this.resourceService.getRestService(); this.restService = this.resourceService.getRestService();
this.examDeletePopup = examDeletePopup; this.examDeletePopup = examDeletePopup;
@ -390,7 +390,7 @@ public class ExamForm implements TemplateComposer {
} }
final boolean proctoringEnabled = importFromQuizData ? false : this.restService final boolean proctoringEnabled = importFromQuizData ? false : this.restService
.getBuilder(GetProctoringSettings.class) .getBuilder(GetExamProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call() .call()
.map(ProctoringServiceSettings::getEnableProctoring) .map(ProctoringServiceSettings::getEnableProctoring)
@ -455,13 +455,13 @@ public class ExamForm implements TemplateComposer {
.newAction(ActionDefinition.EXAM_PROCTORING_ON) .newAction(ActionDefinition.EXAM_PROCTORING_ON)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant && editable)) .withExec(this.proctoringSettingsPopup.settingsFunction(this.pageService, modifyGrant && editable))
.noEventPropagation() .noEventPropagation()
.publishIf(() -> proctoringEnabled && readonly) .publishIf(() -> proctoringEnabled && readonly)
.newAction(ActionDefinition.EXAM_PROCTORING_OFF) .newAction(ActionDefinition.EXAM_PROCTORING_OFF)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant && editable)) .withExec(this.proctoringSettingsPopup.settingsFunction(this.pageService, modifyGrant && editable))
.noEventPropagation() .noEventPropagation()
.publishIf(() -> !proctoringEnabled && readonly); .publishIf(() -> !proctoringEnabled && readonly);

View file

@ -44,15 +44,15 @@ import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog; 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.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplateProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamTemplateProctoringSettings;
@Lazy @Lazy
@Component @Component
@GuiProfile @GuiProfile
public class ExamProctoringSettings { public class ExamTemplateProctoringSettingsPopup {
private static final Logger log = LoggerFactory.getLogger(ExamProctoringSettings.class); private static final Logger log = LoggerFactory.getLogger(ExamTemplateProctoringSettingsPopup.class);
private final static LocTextKey SEB_PROCTORING_FORM_TITLE = private final static LocTextKey SEB_PROCTORING_FORM_TITLE =
new LocTextKey("sebserver.exam.proctoring.form.title"); new LocTextKey("sebserver.exam.proctoring.form.title");
@ -178,7 +178,7 @@ public class ExamProctoringSettings {
final boolean saveOk = !pageService final boolean saveOk = !pageService
.getRestService() .getRestService()
.getBuilder(SaveProctoringSettings.class) .getBuilder(SaveExamTemplateProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.withBody(examProctoring) .withBody(examProctoring)
.call() .call()
@ -227,7 +227,7 @@ public class ExamProctoringSettings {
.createPopupScrollComposite(parent); .createPopupScrollComposite(parent);
final ProctoringServiceSettings proctoringSettings = restService final ProctoringServiceSettings proctoringSettings = restService
.getBuilder(GetProctoringSettings.class) .getBuilder(GetExamTemplateProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call() .call()
.getOrThrow(); .getOrThrow();
@ -329,24 +329,6 @@ public class ExamProctoringSettings {
return () -> formHandle; return () -> formHandle;
} }
// TODO
// private void procServiceSelection(final Form form) {
// final ProctoringServerType proctoringServerType = ProctoringServerType
// .valueOf(form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_TYPE));
// switch (proctoringServerType) {
// case ZOOM: {
// form.setFieldVisible(true, ProctoringServiceSettings.ATTR_SDK_KEY);
// form.setFieldVisible(true, ProctoringServiceSettings.ATTR_SDK_SECRET);
// break;
// }
// default: {
// form.setFieldVisible(false, ProctoringServiceSettings.ATTR_SDK_KEY);
// form.setFieldVisible(false, ProctoringServiceSettings.ATTR_SDK_SECRET);
// }
// }
// }
} }
} }

View file

@ -0,0 +1,343 @@
/*
* Copyright (c) 2020 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.Arrays;
import java.util.EnumSet;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.EntityKey;
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.ProctoringServerType;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
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.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.event.ActionEvent;
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.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplateProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamTemplateProctoringSettings;
@Lazy
@Component
@GuiProfile
public class ProctoringSettingsPopup {
private static final Logger log = LoggerFactory.getLogger(ProctoringSettingsPopup.class);
private final static LocTextKey SEB_PROCTORING_FORM_TITLE =
new LocTextKey("sebserver.exam.proctoring.form.title");
private final static LocTextKey SEB_PROCTORING_FORM_INFO =
new LocTextKey("sebserver.exam.proctoring.form.info");
private final static LocTextKey SEB_PROCTORING_FORM_INFO_TITLE =
new LocTextKey("sebserver.exam.proctoring.form.info.title");
private final static LocTextKey SEB_PROCTORING_FORM_ENABLE =
new LocTextKey("sebserver.exam.proctoring.form.enabled");
private final static LocTextKey SEB_PROCTORING_FORM_TYPE =
new LocTextKey("sebserver.exam.proctoring.form.type");
private final static LocTextKey SEB_PROCTORING_FORM_URL =
new LocTextKey("sebserver.exam.proctoring.form.url");
private final static LocTextKey SEB_PROCTORING_FORM_ROOM_SIZE =
new LocTextKey("sebserver.exam.proctoring.form.collectingRoomSize");
private final static LocTextKey SEB_PROCTORING_FORM_APPKEY =
new LocTextKey("sebserver.exam.proctoring.form.appkey");
private final static LocTextKey SEB_PROCTORING_FORM_SECRET =
new LocTextKey("sebserver.exam.proctoring.form.secret");
private final static LocTextKey SEB_PROCTORING_FORM_SDKKEY =
new LocTextKey("sebserver.exam.proctoring.form.sdkkey");
private final static LocTextKey SEB_PROCTORING_FORM_SDKSECRET =
new LocTextKey("sebserver.exam.proctoring.form.sdksecret");
private final static LocTextKey SEB_PROCTORING_FORM_USE_ZOOM_APP_CLIENT =
new LocTextKey("sebserver.exam.proctoring.form.useZoomAppClient");
private final static LocTextKey SEB_PROCTORING_FORM_FEATURES =
new LocTextKey("sebserver.exam.proctoring.form.features");
Function<PageAction, PageAction> settingsFunction(final PageService pageService, final boolean modifyGrant) {
return action -> {
final PageContext pageContext = action.pageContext()
.withAttribute(
PageContext.AttributeKeys.FORCE_READ_ONLY,
(modifyGrant) ? Constants.FALSE_STRING : Constants.TRUE_STRING);
final ModalInputDialog<FormHandle<?>> dialog =
new ModalInputDialog<FormHandle<?>>(
action.pageContext().getParent().getShell(),
pageService.getWidgetFactory())
.setDialogWidth(740)
.setDialogHeight(400);
final SEBProctoringPropertiesForm bindFormContext = new SEBProctoringPropertiesForm(
pageService,
pageContext);
final Predicate<FormHandle<?>> doBind = formHandle -> doSaveSettings(
pageService,
pageContext,
formHandle);
if (modifyGrant) {
dialog.open(
SEB_PROCTORING_FORM_TITLE,
doBind,
Utils.EMPTY_EXECUTION,
bindFormContext);
} else {
dialog.open(
SEB_PROCTORING_FORM_TITLE,
pageContext,
pc -> bindFormContext.compose(pc.getParent()));
}
return action;
};
}
private boolean doSaveSettings(
final PageService pageService,
final PageContext pageContext,
final FormHandle<?> formHandle) {
final boolean isReadonly = BooleanUtils.toBoolean(
pageContext.getAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY));
if (isReadonly) {
return true;
}
final EntityKey entityKey = pageContext.getEntityKey();
ProctoringServiceSettings examProctoring = null;
try {
final Form form = formHandle.getForm();
form.clearErrors();
final boolean enabled = BooleanUtils.toBoolean(
form.getFieldValue(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING));
final ProctoringServerType serverType = ProctoringServerType
.valueOf(form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_TYPE));
final String features = form.getFieldValue(ProctoringServiceSettings.ATTR_ENABLED_FEATURES);
final EnumSet<ProctoringFeature> featureFlags = (StringUtils.isNotBlank(features))
? EnumSet.copyOf(Arrays.asList(StringUtils.split(features, Constants.LIST_SEPARATOR))
.stream()
.map(str -> ProctoringFeature.valueOf(str))
.collect(Collectors.toSet()))
: EnumSet.noneOf(ProctoringFeature.class);
examProctoring = new ProctoringServiceSettings(
Long.parseLong(entityKey.modelId),
enabled,
serverType,
form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_URL),
Integer.parseInt(form.getFieldValue(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE)),
featureFlags,
false,
form.getFieldValue(ProctoringServiceSettings.ATTR_APP_KEY),
form.getFieldValue(ProctoringServiceSettings.ATTR_APP_SECRET),
form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_KEY),
form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_SECRET),
BooleanUtils.toBoolean(form.getFieldValue(
ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM)));
} catch (final Exception e) {
log.error("Unexpected error while trying to get settings from form: ", e);
}
if (examProctoring == null) {
return false;
}
final boolean saveOk = !pageService
.getRestService()
.getBuilder(
entityKey.entityType == EntityType.EXAM
? SaveExamProctoringSettings.class
: SaveExamTemplateProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.withBody(examProctoring)
.call()
.onError(formHandle::handleError)
.hasError();
if (saveOk) {
final PageAction action = pageService.pageActionBuilder(pageContext)
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
.create();
pageService.firePageEvent(
new ActionEvent(action),
action.pageContext());
return true;
}
return false;
}
private final class SEBProctoringPropertiesForm
implements ModalInputDialogComposer<FormHandle<?>> {
private final PageService pageService;
private final PageContext pageContext;
protected SEBProctoringPropertiesForm(
final PageService pageService,
final PageContext pageContext) {
this.pageService = pageService;
this.pageContext = pageContext;
}
@Override
public Supplier<FormHandle<?>> compose(final Composite parent) {
final RestService restService = this.pageService.getRestService();
final ResourceService resourceService = this.pageService.getResourceService();
final EntityKey entityKey = this.pageContext.getEntityKey();
final boolean isReadonly = BooleanUtils.toBoolean(
this.pageContext.getAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY));
final Composite content = this.pageService
.getWidgetFactory()
.createPopupScrollComposite(parent);
final ProctoringServiceSettings proctoringSettings = restService
.getBuilder(
entityKey.entityType == EntityType.EXAM
? GetExamProctoringSettings.class
: GetExamTemplateProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.getOrThrow();
final PageContext formContext = this.pageContext
.copyOf(content)
.clearEntityKeys();
final FormHandle<ProctoringServiceSettings> formHandle = this.pageService.formBuilder(
formContext)
.withDefaultSpanInput(5)
.withEmptyCellSeparation(true)
.withDefaultSpanEmptyCell(1)
.readonly(isReadonly)
.addField(FormBuilder.text(
"Info",
SEB_PROCTORING_FORM_INFO_TITLE,
this.pageService.getI18nSupport().getText(SEB_PROCTORING_FORM_INFO))
.asArea(50)
.asHTML()
.readonly(true))
.addField(FormBuilder.checkbox(
ProctoringServiceSettings.ATTR_ENABLE_PROCTORING,
SEB_PROCTORING_FORM_ENABLE,
String.valueOf(proctoringSettings.enableProctoring)))
.addField(FormBuilder.singleSelection(
ProctoringServiceSettings.ATTR_SERVER_TYPE,
SEB_PROCTORING_FORM_TYPE,
proctoringSettings.serverType.name(),
resourceService::examProctoringTypeResources))
.addField(FormBuilder.text(
ProctoringServiceSettings.ATTR_SERVER_URL,
SEB_PROCTORING_FORM_URL,
proctoringSettings.serverURL)
.mandatory())
.addField(FormBuilder.text(
ProctoringServiceSettings.ATTR_APP_KEY,
SEB_PROCTORING_FORM_APPKEY,
proctoringSettings.appKey)
.mandatory())
.withEmptyCellSeparation(false)
.addField(FormBuilder.password(
ProctoringServiceSettings.ATTR_APP_SECRET,
SEB_PROCTORING_FORM_SECRET,
(proctoringSettings.appSecret != null)
? String.valueOf(proctoringSettings.appSecret)
: null)
.mandatory())
.addField(FormBuilder.text(
ProctoringServiceSettings.ATTR_SDK_KEY,
SEB_PROCTORING_FORM_SDKKEY,
proctoringSettings.sdkKey))
.withEmptyCellSeparation(false)
.addField(FormBuilder.password(
ProctoringServiceSettings.ATTR_SDK_SECRET,
SEB_PROCTORING_FORM_SDKSECRET,
(proctoringSettings.sdkSecret != null)
? String.valueOf(proctoringSettings.sdkSecret)
: null))
.withDefaultSpanInput(1)
.addField(FormBuilder.text(
ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE,
SEB_PROCTORING_FORM_ROOM_SIZE,
String.valueOf(proctoringSettings.getCollectingRoomSize()))
.asNumber(numString -> Long.parseLong(numString)))
.withEmptyCellSeparation(true)
.withDefaultSpanEmptyCell(4)
.withDefaultSpanInput(5)
.addField(FormBuilder.checkbox(
ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM,
SEB_PROCTORING_FORM_USE_ZOOM_APP_CLIENT,
String.valueOf(proctoringSettings.useZoomAppClientForCollectingRoom)))
.withDefaultSpanInput(5)
.withEmptyCellSeparation(true)
.withDefaultSpanEmptyCell(1)
.addField(FormBuilder.multiCheckboxSelection(
ProctoringServiceSettings.ATTR_ENABLED_FEATURES,
SEB_PROCTORING_FORM_FEATURES,
StringUtils.join(proctoringSettings.enabledFeatures, Constants.LIST_SEPARATOR),
resourceService::examProctoringFeaturesResources))
.build();
if (proctoringSettings.serviceInUse) {
formHandle.getForm().getFieldInput(ProctoringServiceSettings.ATTR_SERVER_TYPE).setEnabled(false);
formHandle.getForm().getFieldInput(ProctoringServiceSettings.ATTR_SERVER_URL).setEnabled(false);
}
return () -> formHandle;
}
}
}

View file

@ -53,7 +53,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; 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.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings;
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;
@ -376,7 +376,7 @@ public class MonitoringClientConnection implements TemplateComposer {
if (connectionData.clientConnection.status == ConnectionStatus.ACTIVE) { if (connectionData.clientConnection.status == ConnectionStatus.ACTIVE) {
final ProctoringServiceSettings proctoringSettings = restService final ProctoringServiceSettings proctoringSettings = restService
.getBuilder(GetProctoringSettings.class) .getBuilder(GetExamProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId) .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
.call() .call()
.onError(error -> log.error("Failed to get ProctoringServiceSettings", error)) .onError(error -> log.error("Failed to get ProctoringServiceSettings", error))

View file

@ -56,7 +56,7 @@ import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; 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.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings;
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; import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
import ch.ethz.seb.sebserver.gui.service.session.FullPageMonitoringGUIUpdate; import ch.ethz.seb.sebserver.gui.service.session.FullPageMonitoringGUIUpdate;
@ -243,7 +243,7 @@ public class MonitoringRunningExam implements TemplateComposer {
isExamSupporter)); isExamSupporter));
final ProctoringServiceSettings proctoringSettings = this.restService final ProctoringServiceSettings proctoringSettings = this.restService
.getBuilder(GetProctoringSettings.class) .getBuilder(GetExamProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call() .call()
.getOr(null); .getOr(null);

View file

@ -32,7 +32,7 @@ import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.GuiServiceInfo; import ch.ethz.seb.sebserver.gui.GuiServiceInfo;
import ch.ethz.seb.sebserver.gui.service.page.PageContext; 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;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@ -69,7 +69,7 @@ public class JitsiMeetProctoringView extends AbstractProctoringView {
.getProctoringGUIService(); .getProctoringGUIService();
final ProctoringServiceSettings proctoringSettings = this.pageService final ProctoringServiceSettings proctoringSettings = this.pageService
.getRestService() .getRestService()
.getBuilder(GetProctoringSettings.class) .getBuilder(GetExamProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, proctoringWindowData.examId) .withURIVariable(API.PARAM_MODEL_ID, proctoringWindowData.examId)
.call() .call()
.onError(error -> log.error("Failed to get ProctoringServiceSettings", error)) .onError(error -> log.error("Failed to get ProctoringServiceSettings", error))

View file

@ -32,7 +32,7 @@ import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.GuiServiceInfo; import ch.ethz.seb.sebserver.gui.GuiServiceInfo;
import ch.ethz.seb.sebserver.gui.service.page.PageContext; 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;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@ -69,7 +69,7 @@ public class ZoomProctoringView extends AbstractProctoringView {
.getProctoringGUIService(); .getProctoringGUIService();
final ProctoringServiceSettings proctoringSettings = this.pageService final ProctoringServiceSettings proctoringSettings = this.pageService
.getRestService() .getRestService()
.getBuilder(GetProctoringSettings.class) .getBuilder(GetExamProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, proctoringWindowData.examId) .withURIVariable(API.PARAM_MODEL_ID, proctoringWindowData.examId)
.call() .call()
.onError(error -> log.error("Failed to get ProctoringServiceSettings", error)) .onError(error -> log.error("Failed to get ProctoringServiceSettings", error))

View file

@ -24,9 +24,9 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy @Lazy
@Component @Component
@GuiProfile @GuiProfile
public class GetProctoringSettings extends RestCall<ProctoringServiceSettings> { public class GetExamProctoringSettings extends RestCall<ProctoringServiceSettings> {
public GetProctoringSettings() { public GetExamProctoringSettings() {
super(new TypeKey<>( super(new TypeKey<>(
CallType.GET_SINGLE, CallType.GET_SINGLE,
EntityType.EXAM_PROCTOR_DATA, EntityType.EXAM_PROCTOR_DATA,

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2020 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 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.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetExamTemplateProctoringSettings extends RestCall<ProctoringServiceSettings> {
public GetExamTemplateProctoringSettings() {
super(new TypeKey<>(
CallType.GET_SINGLE,
EntityType.EXAM_PROCTOR_DATA,
new TypeReference<ProctoringServiceSettings>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_JSON,
API.EXAM_TEMPLATE_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT);
}
}

View file

@ -17,20 +17,20 @@ import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy @Lazy
@Component @Component
@GuiProfile @GuiProfile
public class SaveProctoringSettings extends RestCall<Exam> { public class SaveExamProctoringSettings extends RestCall<ProctoringServiceSettings> {
public SaveProctoringSettings() { public SaveExamProctoringSettings() {
super(new TypeKey<>( super(new TypeKey<>(
CallType.SAVE, CallType.SAVE,
EntityType.EXAM_PROCTOR_DATA, EntityType.EXAM_PROCTOR_DATA,
new TypeReference<Exam>() { new TypeReference<ProctoringServiceSettings>() {
}), }),
HttpMethod.POST, HttpMethod.POST,
MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON,

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2020 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 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.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class SaveExamTemplateProctoringSettings extends RestCall<ProctoringServiceSettings> {
public SaveExamTemplateProctoringSettings() {
super(new TypeKey<>(
CallType.SAVE,
EntityType.EXAM_PROCTOR_DATA,
new TypeReference<ProctoringServiceSettings>() {
}),
HttpMethod.POST,
MediaType.APPLICATION_JSON,
API.EXAM_TEMPLATE_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT);
}
}

View file

@ -13,6 +13,7 @@ import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
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.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
@ -22,6 +23,18 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttribut
* in a separated data-base table. */ * in a separated data-base table. */
public interface AdditionalAttributesDAO { public interface AdditionalAttributesDAO {
/** Use this to get all additional attribute records for a specific entity.
*
* @param type the entity type
* @param entityId the entity identifier (primary key)
* @return Result refer to the collection of additional attribute records or to an error if happened */
default Result<Collection<AdditionalAttributeRecord>> getAdditionalAttributes(final EntityKey entityKey) {
return Result.tryCatch(() -> getAdditionalAttributes(
entityKey.entityType,
Long.valueOf(entityKey.modelId))
.getOrThrow());
}
/** Use this to get all additional attribute records for a specific entity. /** Use this to get all additional attribute records for a specific entity.
* *
* @param type the entity type * @param type the entity type

View file

@ -12,7 +12,6 @@ import org.apache.commons.lang3.BooleanUtils;
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.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService;
@ -83,19 +82,10 @@ public interface ExamAdminService {
* @return Result refer to proctoring is enabled flag or to an error when happened. */ * @return Result refer to proctoring is enabled flag or to an error when happened. */
Result<Boolean> isProctoringEnabled(final Long examId); Result<Boolean> isProctoringEnabled(final Long examId);
/** Get the exam proctoring service implementation of specified type.
*
* @param type exam proctoring service server type
* @return ExamProctoringService instance */
Result<ExamProctoringService> getExamProctoringService(final ProctoringServerType type);
/** Get the exam proctoring service implementation for specified exam. /** Get the exam proctoring service implementation for specified exam.
* *
* @param examId the exam identifier * @param examId the exam identifier
* @return ExamProctoringService instance */ * @return ExamProctoringService instance */
default Result<ExamProctoringService> getExamProctoringService(final Long examId) { Result<ExamProctoringService> getExamProctoringService(final Long examId);
return getProctoringServiceSettings(examId)
.flatMap(settings -> getExamProctoringService(settings.serverType));
}
} }

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.servicelayer.exam;
import java.util.EnumSet;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService;
public interface ProctoringAdminService {
EnumSet<EntityType> SUPPORTED_PARENT_ENTITES = EnumSet.of(
EntityType.EXAM,
EntityType.EXAM_TEMPLATE);
/** Get proctoring service settings for a certain entity (SUPPORTED_PARENT_ENTITES).
*
* @param parentEntityKey the entity key of the parent entity to get the proctoring service settings from
* @return Result refer to proctoring service settings or to an error when happened. */
Result<ProctoringServiceSettings> getProctoringSettings(EntityKey parentEntityKey);
/** Save the given proctoring service settings for a certain entity (SUPPORTED_PARENT_ENTITES).
*
* @param parentEntityKey the entity key of the parent entity to save the proctoring service settings to
* @param proctoringServiceSettings The proctoring service settings to save
* @return Result refer to saved proctoring service settings or to an error when happened. */
Result<ProctoringServiceSettings> saveProctoringServiceSettings(
EntityKey parentEntityKey,
ProctoringServiceSettings proctoringServiceSettings);
/** Get the exam proctoring service implementation of specified type.
*
* @param type exam proctoring service server type
* @return ExamProctoringService instance */
Result<ExamProctoringService> getExamProctoringService(final ProctoringServerType type);
/** Use this to test the proctoring service settings against the remote proctoring server.
*
* @param proctoringSettings the settings to test
* @return Result refer to true if the settings are correct and the proctoring server can be accessed. */
default Result<Boolean> testExamProctoring(final ProctoringServiceSettings proctoringSettings) {
return getExamProctoringService(proctoringSettings.serverType)
.flatMap(service -> service.testExamProctoring(proctoringSettings));
}
}

View file

@ -9,12 +9,7 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -26,28 +21,23 @@ import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
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.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction; import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction;
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.ProctoringServerType;
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.institution.LmsSetup; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
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.webservice.datalayer.batis.model.AdditionalAttributeRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; 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; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SEBRestrictionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.ExamProctoringServiceFactory;
@Lazy @Lazy
@Service @Service
@ -57,26 +47,20 @@ public class ExamAdminServiceImpl implements ExamAdminService {
private static final Logger log = LoggerFactory.getLogger(ExamAdminServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(ExamAdminServiceImpl.class);
private final ExamDAO examDAO; private final ExamDAO examDAO;
private final ProctoringAdminService proctoringServiceSettingsService;
private final AdditionalAttributesDAO additionalAttributesDAO; private final AdditionalAttributesDAO additionalAttributesDAO;
private final LmsAPIService lmsAPIService; private final LmsAPIService lmsAPIService;
private final Cryptor cryptor;
private final ExamProctoringServiceFactory examProctoringServiceFactory;
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
protected ExamAdminServiceImpl( protected ExamAdminServiceImpl(
final ExamDAO examDAO, final ExamDAO examDAO,
final ProctoringAdminService proctoringServiceSettingsService,
final AdditionalAttributesDAO additionalAttributesDAO, final AdditionalAttributesDAO additionalAttributesDAO,
final LmsAPIService lmsAPIService, final LmsAPIService lmsAPIService) {
final Cryptor cryptor,
final ExamProctoringServiceFactory examProctoringServiceFactory,
final RemoteProctoringRoomDAO remoteProctoringRoomDAO) {
this.examDAO = examDAO; this.examDAO = examDAO;
this.proctoringServiceSettingsService = proctoringServiceSettingsService;
this.additionalAttributesDAO = additionalAttributesDAO; this.additionalAttributesDAO = additionalAttributesDAO;
this.lmsAPIService = lmsAPIService; this.lmsAPIService = lmsAPIService;
this.cryptor = cryptor;
this.examProctoringServiceFactory = examProctoringServiceFactory;
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
} }
@Override @Override
@ -135,26 +119,7 @@ public class ExamAdminServiceImpl implements ExamAdminService {
@Override @Override
public Result<ProctoringServiceSettings> getProctoringServiceSettings(final Long examId) { public Result<ProctoringServiceSettings> getProctoringServiceSettings(final Long examId) {
return this.additionalAttributesDAO.getAdditionalAttributes(EntityType.EXAM, examId) return this.proctoringServiceSettingsService.getProctoringSettings(new EntityKey(examId, EntityType.EXAM));
.map(attrs -> attrs.stream()
.collect(Collectors.toMap(
attr -> attr.getName(),
Function.identity())))
.map(mapping -> {
return new ProctoringServiceSettings(
examId,
getEnabled(mapping),
getServerType(mapping),
getString(mapping, ProctoringServiceSettings.ATTR_SERVER_URL),
getCollectingRoomSize(mapping),
getEnabledFeatures(mapping),
this.remoteProctoringRoomDAO.isServiceInUse(examId).getOr(true),
getString(mapping, ProctoringServiceSettings.ATTR_APP_KEY),
getString(mapping, ProctoringServiceSettings.ATTR_APP_SECRET),
getString(mapping, ProctoringServiceSettings.ATTR_SDK_KEY),
getString(mapping, ProctoringServiceSettings.ATTR_SDK_SECRET),
getBoolean(mapping, ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM));
});
} }
@Override @Override
@ -163,79 +128,14 @@ public class ExamAdminServiceImpl implements ExamAdminService {
final Long examId, final Long examId,
final ProctoringServiceSettings proctoringServiceSettings) { final ProctoringServiceSettings proctoringServiceSettings) {
return Result.tryCatch(() -> { return this.proctoringServiceSettingsService
.saveProctoringServiceSettings(
this.additionalAttributesDAO.saveAdditionalAttribute( new EntityKey(examId, EntityType.EXAM),
EntityType.EXAM, proctoringServiceSettings)
examId, .map(settings -> {
ProctoringServiceSettings.ATTR_ENABLE_PROCTORING, this.examDAO.setModified(examId);
String.valueOf(proctoringServiceSettings.enableProctoring)); return settings;
});
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_SERVER_TYPE,
proctoringServiceSettings.serverType.name());
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_SERVER_URL,
StringUtils.trim(proctoringServiceSettings.serverURL));
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE,
String.valueOf(proctoringServiceSettings.collectingRoomSize));
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_APP_KEY,
StringUtils.trim(proctoringServiceSettings.appKey));
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_APP_SECRET,
this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.appSecret))
.getOrThrow()
.toString());
if (StringUtils.isNotBlank(proctoringServiceSettings.sdkKey)) {
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_SDK_KEY,
StringUtils.trim(proctoringServiceSettings.sdkKey));
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_SDK_SECRET,
this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.sdkSecret))
.getOrThrow()
.toString());
}
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_ENABLED_FEATURES,
StringUtils.join(proctoringServiceSettings.enabledFeatures, Constants.LIST_SEPARATOR));
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM,
String.valueOf(proctoringServiceSettings.useZoomAppClientForCollectingRoom));
// Mark the involved exam as updated to notify changes
this.examDAO.setModified(examId);
return proctoringServiceSettings;
});
} }
@Override @Override
@ -256,78 +156,10 @@ public class ExamAdminServiceImpl implements ExamAdminService {
} }
@Override @Override
public Result<ExamProctoringService> getExamProctoringService(final ProctoringServerType type) { public Result<ExamProctoringService> getExamProctoringService(final Long examId) {
return this.examProctoringServiceFactory return getProctoringServiceSettings(examId)
.getExamProctoringService(type); .flatMap(settings -> this.proctoringServiceSettingsService
} .getExamProctoringService(settings.serverType));
private Boolean getEnabled(final Map<String, AdditionalAttributeRecord> mapping) {
if (mapping.containsKey(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING)) {
return BooleanUtils.toBoolean(mapping.get(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING).getValue());
} else {
return false;
}
}
private ProctoringServerType getServerType(final Map<String, AdditionalAttributeRecord> mapping) {
if (mapping.containsKey(ProctoringServiceSettings.ATTR_SERVER_TYPE)) {
return ProctoringServerType.valueOf(mapping.get(ProctoringServiceSettings.ATTR_SERVER_TYPE).getValue());
} else {
return ProctoringServerType.JITSI_MEET;
}
}
private String getString(final Map<String, AdditionalAttributeRecord> mapping, final String name) {
if (mapping.containsKey(name)) {
return mapping.get(name).getValue();
} else {
return null;
}
}
private Boolean getBoolean(final Map<String, AdditionalAttributeRecord> mapping, final String name) {
if (mapping.containsKey(name)) {
return BooleanUtils.toBooleanObject(mapping.get(name).getValue());
} else {
return false;
}
}
private Integer getCollectingRoomSize(final Map<String, AdditionalAttributeRecord> mapping) {
if (mapping.containsKey(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE)) {
return Integer.valueOf(mapping.get(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE).getValue());
} else {
return 20;
}
}
private EnumSet<ProctoringFeature> getEnabledFeatures(final Map<String, AdditionalAttributeRecord> mapping) {
if (mapping.containsKey(ProctoringServiceSettings.ATTR_ENABLED_FEATURES)) {
try {
final String value = mapping.get(ProctoringServiceSettings.ATTR_ENABLED_FEATURES).getValue();
return StringUtils.isNotBlank(value)
? EnumSet.copyOf(Arrays.asList(StringUtils.split(value, Constants.LIST_SEPARATOR))
.stream()
.map(str -> {
try {
return ProctoringFeature.valueOf(str);
} catch (final Exception e) {
log.error(
"Failed to enabled single features for proctoring settings. Skipping. {}",
e.getMessage());
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toSet()))
: EnumSet.noneOf(ProctoringFeature.class);
} catch (final Exception e) {
log.error("Failed to get enabled features for proctoring settings. Enable all. {}", e.getMessage());
return EnumSet.allOf(ProctoringFeature.class);
}
} else {
return EnumSet.allOf(ProctoringFeature.class);
}
} }
private Result<Exam> saveAdditionalAttributesForMoodleExams(final Exam exam) { private Result<Exam> saveAdditionalAttributesForMoodleExams(final Exam exam) {

View file

@ -118,7 +118,8 @@ public class ExamTemplateServiceImpl implements ExamTemplateService {
@Override @Override
public Result<Exam> initAdditionalAttributes(final Exam exam) { public Result<Exam> initAdditionalAttributes(final Exam exam) {
return this.examAdminService.saveLMSAttributes(exam) return this.examAdminService
.saveLMSAttributes(exam)
.map(_exam -> { .map(_exam -> {
if (exam.examTemplateId != null) { if (exam.examTemplateId != null) {
@ -148,7 +149,8 @@ public class ExamTemplateServiceImpl implements ExamTemplateService {
} }
} }
return _exam; return _exam;
}).onError(error -> log.error("Failed to create additional attributes defined by template for exam: ", }).onError(error -> log.error(
"Failed to create additional attributes defined by template for exam: ",
error)); error));
} }

View file

@ -0,0 +1,270 @@
/*
* Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
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.ProctoringServerType;
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.Utils;
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.RemoteProctoringRoomDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.ExamProctoringServiceFactory;
@Lazy
@Service
@WebServiceProfile
public class ProctoringAdminServiceImpl implements ProctoringAdminService {
private static final Logger log = LoggerFactory.getLogger(ProctoringAdminServiceImpl.class);
private final AdditionalAttributesDAO additionalAttributesDAO;
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
private final ExamProctoringServiceFactory examProctoringServiceFactory;
private final Cryptor cryptor;
public ProctoringAdminServiceImpl(
final AdditionalAttributesDAO additionalAttributesDAO,
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
final ExamProctoringServiceFactory examProctoringServiceFactory,
final Cryptor cryptor) {
this.additionalAttributesDAO = additionalAttributesDAO;
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
this.examProctoringServiceFactory = examProctoringServiceFactory;
this.cryptor = cryptor;
}
@Override
@Transactional(readOnly = true)
public Result<ProctoringServiceSettings> getProctoringSettings(final EntityKey parentEntityKey) {
return Result.tryCatch(() -> {
final Long entityId = Long.parseLong(parentEntityKey.modelId);
checkType(parentEntityKey);
return this.additionalAttributesDAO
.getAdditionalAttributes(parentEntityKey.entityType, entityId)
.map(attrs -> attrs.stream()
.collect(Collectors.toMap(
attr -> attr.getName(),
Function.identity())))
.map(mapping -> {
return new ProctoringServiceSettings(
entityId,
getEnabled(mapping),
getServerType(mapping),
getString(mapping, ProctoringServiceSettings.ATTR_SERVER_URL),
getCollectingRoomSize(mapping),
getEnabledFeatures(mapping),
parentEntityKey.entityType == EntityType.EXAM
? this.remoteProctoringRoomDAO.isServiceInUse(entityId).getOr(true)
: false,
getString(mapping, ProctoringServiceSettings.ATTR_APP_KEY),
getString(mapping, ProctoringServiceSettings.ATTR_APP_SECRET),
getString(mapping, ProctoringServiceSettings.ATTR_SDK_KEY),
getString(mapping, ProctoringServiceSettings.ATTR_SDK_SECRET),
getBoolean(mapping,
ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM));
})
.getOrThrow();
});
}
@Override
@Transactional
public Result<ProctoringServiceSettings> saveProctoringServiceSettings(
final EntityKey parentEntityKey,
final ProctoringServiceSettings proctoringServiceSettings) {
return Result.tryCatch(() -> {
final Long entityId = Long.parseLong(parentEntityKey.modelId);
checkType(parentEntityKey);
if (StringUtils.isNotBlank(proctoringServiceSettings.serverURL)) {
testExamProctoring(proctoringServiceSettings).getOrThrow();
}
this.additionalAttributesDAO.saveAdditionalAttribute(
parentEntityKey.entityType,
entityId,
ProctoringServiceSettings.ATTR_ENABLE_PROCTORING,
String.valueOf(proctoringServiceSettings.enableProctoring));
this.additionalAttributesDAO.saveAdditionalAttribute(
parentEntityKey.entityType,
entityId,
ProctoringServiceSettings.ATTR_SERVER_TYPE,
proctoringServiceSettings.serverType.name());
this.additionalAttributesDAO.saveAdditionalAttribute(
parentEntityKey.entityType,
entityId,
ProctoringServiceSettings.ATTR_SERVER_URL,
StringUtils.trim(proctoringServiceSettings.serverURL));
this.additionalAttributesDAO.saveAdditionalAttribute(
parentEntityKey.entityType,
entityId,
ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE,
String.valueOf(proctoringServiceSettings.collectingRoomSize));
this.additionalAttributesDAO.saveAdditionalAttribute(
parentEntityKey.entityType,
entityId,
ProctoringServiceSettings.ATTR_APP_KEY,
StringUtils.trim(proctoringServiceSettings.appKey));
this.additionalAttributesDAO.saveAdditionalAttribute(
parentEntityKey.entityType,
entityId,
ProctoringServiceSettings.ATTR_APP_SECRET,
this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.appSecret))
.getOrThrow()
.toString());
if (StringUtils.isNotBlank(proctoringServiceSettings.sdkKey)) {
this.additionalAttributesDAO.saveAdditionalAttribute(
parentEntityKey.entityType,
entityId,
ProctoringServiceSettings.ATTR_SDK_KEY,
StringUtils.trim(proctoringServiceSettings.sdkKey));
this.additionalAttributesDAO.saveAdditionalAttribute(
parentEntityKey.entityType,
entityId,
ProctoringServiceSettings.ATTR_SDK_SECRET,
this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.sdkSecret))
.getOrThrow()
.toString());
}
this.additionalAttributesDAO.saveAdditionalAttribute(
parentEntityKey.entityType,
entityId,
ProctoringServiceSettings.ATTR_ENABLED_FEATURES,
StringUtils.join(proctoringServiceSettings.enabledFeatures, Constants.LIST_SEPARATOR));
this.additionalAttributesDAO.saveAdditionalAttribute(
parentEntityKey.entityType,
entityId,
ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM,
String.valueOf(proctoringServiceSettings.useZoomAppClientForCollectingRoom));
return proctoringServiceSettings;
});
}
@Override
public Result<ExamProctoringService> getExamProctoringService(final ProctoringServerType type) {
return this.examProctoringServiceFactory
.getExamProctoringService(type);
}
private void checkType(final EntityKey parentEntityKey) {
if (!SUPPORTED_PARENT_ENTITES.contains(parentEntityKey.entityType)) {
throw new UnsupportedOperationException(
"No proctoring service settings supported for entity: " + parentEntityKey);
}
}
private Boolean getEnabled(final Map<String, AdditionalAttributeRecord> mapping) {
if (mapping.containsKey(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING)) {
return BooleanUtils.toBoolean(mapping.get(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING).getValue());
} else {
return false;
}
}
private ProctoringServerType getServerType(final Map<String, AdditionalAttributeRecord> mapping) {
if (mapping.containsKey(ProctoringServiceSettings.ATTR_SERVER_TYPE)) {
return ProctoringServerType.valueOf(mapping.get(ProctoringServiceSettings.ATTR_SERVER_TYPE).getValue());
} else {
return ProctoringServerType.JITSI_MEET;
}
}
private String getString(final Map<String, AdditionalAttributeRecord> mapping, final String name) {
if (mapping.containsKey(name)) {
return mapping.get(name).getValue();
} else {
return null;
}
}
private Boolean getBoolean(final Map<String, AdditionalAttributeRecord> mapping, final String name) {
if (mapping.containsKey(name)) {
return BooleanUtils.toBooleanObject(mapping.get(name).getValue());
} else {
return false;
}
}
private Integer getCollectingRoomSize(final Map<String, AdditionalAttributeRecord> mapping) {
if (mapping.containsKey(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE)) {
return Integer.valueOf(mapping.get(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE).getValue());
} else {
return 20;
}
}
private EnumSet<ProctoringFeature> getEnabledFeatures(final Map<String, AdditionalAttributeRecord> mapping) {
if (mapping.containsKey(ProctoringServiceSettings.ATTR_ENABLED_FEATURES)) {
try {
final String value = mapping.get(ProctoringServiceSettings.ATTR_ENABLED_FEATURES).getValue();
return StringUtils.isNotBlank(value)
? EnumSet.copyOf(Arrays.asList(StringUtils.split(value, Constants.LIST_SEPARATOR))
.stream()
.map(str -> {
try {
return ProctoringFeature.valueOf(str);
} catch (final Exception e) {
log.error(
"Failed to enabled single features for proctoring settings. Skipping. {}",
e.getMessage());
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toSet()))
: EnumSet.noneOf(ProctoringFeature.class);
} catch (final Exception e) {
log.error("Failed to get enabled features for proctoring settings. Enable all. {}", e.getMessage());
return EnumSet.allOf(ProctoringFeature.class);
}
} else {
return EnumSet.allOf(ProctoringFeature.class);
}
}
}

View file

@ -40,6 +40,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamDeletionEvent; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamDeletionEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; 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.session.ExamFinishedEvent; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamFinishedEvent;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringRoomService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringRoomService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService;
@ -58,6 +59,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO; private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
private final ClientConnectionDAO clientConnectionDAO; private final ClientConnectionDAO clientConnectionDAO;
private final ExamAdminService examAdminService; private final ExamAdminService examAdminService;
private final ProctoringAdminService proctoringAdminService;
private final ExamSessionService examSessionService; private final ExamSessionService examSessionService;
private final SEBClientInstructionService sebInstructionService; private final SEBClientInstructionService sebInstructionService;
private final boolean sendBroadcastReset; private final boolean sendBroadcastReset;
@ -66,6 +68,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
final RemoteProctoringRoomDAO remoteProctoringRoomDAO, final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
final ClientConnectionDAO clientConnectionDAO, final ClientConnectionDAO clientConnectionDAO,
final ExamAdminService examAdminService, final ExamAdminService examAdminService,
final ProctoringAdminService proctoringAdminService,
final ExamSessionService examSessionService, final ExamSessionService examSessionService,
final SEBClientInstructionService sebInstructionService, final SEBClientInstructionService sebInstructionService,
@Value("${sebserver.webservice.proctoring.resetBroadcastOnLeav:true}") final boolean sendBroadcastReset) { @Value("${sebserver.webservice.proctoring.resetBroadcastOnLeav:true}") final boolean sendBroadcastReset) {
@ -73,6 +76,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO; this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
this.clientConnectionDAO = clientConnectionDAO; this.clientConnectionDAO = clientConnectionDAO;
this.examAdminService = examAdminService; this.examAdminService = examAdminService;
this.proctoringAdminService = proctoringAdminService;
this.examSessionService = examSessionService; this.examSessionService = examSessionService;
this.sebInstructionService = sebInstructionService; this.sebInstructionService = sebInstructionService;
this.sendBroadcastReset = sendBroadcastReset; this.sendBroadcastReset = sendBroadcastReset;
@ -146,7 +150,8 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
event.ids.forEach(examId -> { event.ids.forEach(examId -> {
try { try {
this.examAdminService.examForPK(examId) this.examAdminService
.examForPK(examId)
.flatMap(this::disposeRoomsForExam) .flatMap(this::disposeRoomsForExam)
.getOrThrow(); .getOrThrow();
@ -176,7 +181,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.getProctoringServiceSettings(exam.id) .getProctoringServiceSettings(exam.id)
.getOrThrow(); .getOrThrow();
this.examAdminService this.proctoringAdminService
.getExamProctoringService(proctoringSettings.serverType) .getExamProctoringService(proctoringSettings.serverType)
.flatMap(service -> service.disposeServiceRoomsForExam(exam.id, proctoringSettings)) .flatMap(service -> service.disposeServiceRoomsForExam(exam.id, proctoringSettings))
.onError(error -> log.error("Failed to dispose proctoring service rooms for exam: {} / {}", .onError(error -> log.error("Failed to dispose proctoring service rooms for exam: {} / {}",
@ -204,7 +209,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.getProctoringServiceSettings(examId) .getProctoringServiceSettings(examId)
.getOrThrow(); .getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService final ExamProctoringService examProctoringService = this.proctoringAdminService
.getExamProctoringService(settings.serverType) .getExamProctoringService(settings.serverType)
.getOrThrow(); .getOrThrow();
@ -242,7 +247,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.getProctoringServiceSettings(examId) .getProctoringServiceSettings(examId)
.getOrThrow(); .getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService final ExamProctoringService examProctoringService = this.proctoringAdminService
.getExamProctoringService(settings.serverType) .getExamProctoringService(settings.serverType)
.getOrThrow(); .getOrThrow();
@ -251,7 +256,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.flatMap(room -> this.remoteProctoringRoomDAO.createBreakOutRoom(examId, room, connectionTokens)) .flatMap(room -> this.remoteProctoringRoomDAO.createBreakOutRoom(examId, room, connectionTokens))
.getOrThrow(); .getOrThrow();
return this.examAdminService return this.proctoringAdminService
.getExamProctoringService(settings.serverType) .getExamProctoringService(settings.serverType)
.map(service -> sendJoinRoomBreakOutInstructions( .map(service -> sendJoinRoomBreakOutInstructions(
settings, settings,
@ -272,7 +277,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.getProctoringServiceSettings(examId) .getProctoringServiceSettings(examId)
.getOrThrow(); .getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService final ExamProctoringService examProctoringService = this.proctoringAdminService
.getExamProctoringService(settings.serverType) .getExamProctoringService(settings.serverType)
.getOrThrow(); .getOrThrow();
@ -357,7 +362,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.getProctoringServiceSettings(examId) .getProctoringServiceSettings(examId)
.getOrThrow(); .getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService final ExamProctoringService examProctoringService = this.proctoringAdminService
.getExamProctoringService(proctoringSettings.serverType) .getExamProctoringService(proctoringSettings.serverType)
.getOrThrow(); .getOrThrow();
@ -401,7 +406,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.getProctoringServiceSettings(examId) .getProctoringServiceSettings(examId)
.getOrThrow(); .getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService final ExamProctoringService examProctoringService = this.proctoringAdminService
.getExamProctoringService(proctoringSettings.serverType) .getExamProctoringService(proctoringSettings.serverType)
.getOrThrow(); .getOrThrow();
@ -574,7 +579,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.getProctoringServiceSettings(examId) .getProctoringServiceSettings(examId)
.getOrThrow(); .getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService final ExamProctoringService examProctoringService = this.proctoringAdminService
.getExamProctoringService(settings.serverType) .getExamProctoringService(settings.serverType)
.getOrThrow(); .getOrThrow();
@ -635,7 +640,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.getProctoringServiceSettings(examId) .getProctoringServiceSettings(examId)
.getOrThrow(); .getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService final ExamProctoringService examProctoringService = this.proctoringAdminService
.getExamProctoringService(settings.serverType) .getExamProctoringService(settings.serverType)
.getOrThrow(); .getOrThrow();
@ -689,7 +694,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
final String roomName, final String roomName,
final String subject) { final String subject) {
final ExamProctoringService examProctoringService = this.examAdminService final ExamProctoringService examProctoringService = this.proctoringAdminService
.getExamProctoringService(proctoringSettings.serverType) .getExamProctoringService(proctoringSettings.serverType)
.getOrThrow(); .getOrThrow();

View file

@ -381,11 +381,6 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
.byPK(examId) .byPK(examId)
.flatMap(this.authorization::checkModify) .flatMap(this.authorization::checkModify)
.map(exam -> { .map(exam -> {
if (StringUtils.isNotBlank(proctoringServiceSettings.serverURL)) {
this.examAdminService.getExamProctoringService(proctoringServiceSettings.serverType)
.flatMap(service -> service.testExamProctoring(proctoringServiceSettings))
.getOrThrow();
}
this.examAdminService.saveProctoringServiceSettings(examId, proctoringServiceSettings); this.examAdminService.saveProctoringServiceSettings(examId, proctoringServiceSettings);
return exam; return exam;
}) })

View file

@ -39,6 +39,7 @@ import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate; import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate;
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.IndicatorTemplate; import ch.ethz.seb.sebserver.gbl.model.exam.IndicatorTemplate;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamTemplateRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamTemplateRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
@ -48,6 +49,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionServic
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamTemplateDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamTemplateDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
@WebServiceProfile @WebServiceProfile
@ -56,6 +58,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
public class ExamTemplateController extends EntityController<ExamTemplate, ExamTemplate> { public class ExamTemplateController extends EntityController<ExamTemplate, ExamTemplate> {
private final ExamTemplateDAO examTemplateDAO; private final ExamTemplateDAO examTemplateDAO;
private final ProctoringAdminService proctoringServiceSettingsService;
protected ExamTemplateController( protected ExamTemplateController(
final AuthorizationService authorization, final AuthorizationService authorization,
@ -63,7 +66,8 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
final ExamTemplateDAO entityDAO, final ExamTemplateDAO entityDAO,
final UserActivityLogDAO userActivityLogDAO, final UserActivityLogDAO userActivityLogDAO,
final PaginationService paginationService, final PaginationService paginationService,
final BeanValidationService beanValidationService) { final BeanValidationService beanValidationService,
final ProctoringAdminService proctoringServiceSettingsService) {
super( super(
authorization, authorization,
@ -74,6 +78,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
beanValidationService); beanValidationService);
this.examTemplateDAO = entityDAO; this.examTemplateDAO = entityDAO;
this.proctoringServiceSettingsService = proctoringServiceSettingsService;
} }
@RequestMapping( @RequestMapping(
@ -93,6 +98,9 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
.getOrThrow(); .getOrThrow();
} }
// ****************************************************************************
// **** Indicator
@RequestMapping( @RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_TEMPLATE_INDICATOR_PATH_SEGMENT, + API.EXAM_TEMPLATE_INDICATOR_PATH_SEGMENT,
@ -225,11 +233,65 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
// check write privilege for requested institution and concrete entityType // check write privilege for requested institution and concrete entityType
this.checkWritePrivilege(institutionId); this.checkWritePrivilege(institutionId);
return this.examTemplateDAO.deleteIndicatorTemplate(parentModelId, modelId) return this.examTemplateDAO
.deleteIndicatorTemplate(parentModelId, modelId)
.flatMap(this.userActivityLogDAO::logDelete) .flatMap(this.userActivityLogDAO::logDelete)
.getOrThrow(); .getOrThrow();
} }
// **** Indicator
// ****************************************************************************
// ****************************************************************************
// **** Proctoring
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ProctoringServiceSettings getProctoringServiceSettings(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable final Long modelId) {
checkReadPrivilege(institutionId);
return this.proctoringServiceSettingsService
.getProctoringSettings(new EntityKey(modelId, EntityType.EXAM_TEMPLATE))
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
public ExamTemplate saveProctoringServiceSettings(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(API.PARAM_MODEL_ID) final Long examId,
@Valid @RequestBody final ProctoringServiceSettings proctoringServiceSettings) {
checkModifyPrivilege(institutionId);
return this.entityDAO
.byPK(examId)
.flatMap(this.authorization::checkModify)
.map(exam -> {
this.proctoringServiceSettingsService.saveProctoringServiceSettings(
new EntityKey(examId, EntityType.EXAM_TEMPLATE),
proctoringServiceSettings);
return exam;
})
.flatMap(this.userActivityLogDAO::logModify)
.getOrThrow();
}
// **** Proctoring
// ****************************************************************************
@Override @Override
protected ExamTemplate createNew(final POSTMapper postParams) { protected ExamTemplate createNew(final POSTMapper postParams) {
final Long institutionId = postParams.getLong(API.PARAM_INSTITUTION_ID); final Long institutionId = postParams.getLong(API.PARAM_INSTITUTION_ID);

View file

@ -132,6 +132,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfi
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamDependencies; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamDependencies;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamNames; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplate;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplatePage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplatePage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplates; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplates;
@ -140,7 +141,6 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicator
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorTemplate;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorTemplatePage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorTemplatePage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetSEBRestrictionSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetSEBRestrictionSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewExamConfigMapping; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewExamConfigMapping;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewExamTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewExamTemplate;
@ -148,10 +148,10 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewIndicator
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewIndicatorTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewIndicatorTemplate;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamConfigMapping; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamConfigMapping;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamTemplate;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveIndicator; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveIndicator;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveIndicatorTemplate; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveIndicatorTemplate;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveSEBRestriction; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveSEBRestriction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.ActivateInstitution; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.ActivateInstitution;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution;
@ -3371,15 +3371,15 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
final RestServiceImpl restService = createRestServiceForUser( final RestServiceImpl restService = createRestServiceForUser(
"admin", "admin",
"admin", "admin",
new GetProctoringSettings(), new GetExamProctoringSettings(),
new SaveProctoringSettings()); new SaveExamProctoringSettings());
final Exam exam = createTestExam("admin", "admin"); final Exam exam = createTestExam("admin", "admin");
assertNotNull(exam); assertNotNull(exam);
assertEquals("Demo Quiz 6 (MOCKUP)", exam.name); assertEquals("Demo Quiz 6 (MOCKUP)", exam.name);
final ProctoringServiceSettings settings = restService final ProctoringServiceSettings settings = restService
.getBuilder(GetProctoringSettings.class) .getBuilder(GetExamProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId()) .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
.call() .call()
.getOrThrow(); .getOrThrow();
@ -3401,16 +3401,16 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
"sdkSecret", "sdkSecret",
false); false);
final Result<Exam> saveCall = restService final Result<ProctoringServiceSettings> saveCall = restService
.getBuilder(SaveProctoringSettings.class) .getBuilder(SaveExamProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId()) .withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
.withBody(newSettings) .withBody(newSettings)
.call(); .call();
if (!saveCall.hasError()) { if (!saveCall.hasError()) {
assertFalse(saveCall.hasError()); assertFalse(saveCall.hasError());
final Exam exam2 = saveCall.get(); final ProctoringServiceSettings settings2 = saveCall.get();
assertEquals(exam2.id, exam.id); assertEquals(settings2.examId, exam.id);
} }
} }

View file

@ -63,7 +63,7 @@ public class ExamProctoringRoomServiceTest extends AdministrationAPIIntegrationT
this.examAdminService.saveProctoringServiceSettings( this.examAdminService.saveProctoringServiceSettings(
2L, 2L,
new ProctoringServiceSettings( new ProctoringServiceSettings(
2L, true, ProctoringServerType.JITSI_MEET, "http://jitsi.ch", 1, null, false, 2L, true, ProctoringServerType.JITSI_MEET, "", 1, null, false,
"app-key", "app.secret", "sdk-key", "sdk.secret", false)); "app-key", "app.secret", "sdk-key", "sdk.secret", false));
assertTrue(this.examAdminService.isProctoringEnabled(2L).get()); assertTrue(this.examAdminService.isProctoringEnabled(2L).get());