From 548d4d132f1de8656e939573ccf604cef846c570 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 6 Aug 2020 17:01:38 +0200 Subject: [PATCH] SEBSERV-139 implementing GUI --- .../ch/ethz/seb/sebserver/gbl/api/API.java | 1 - .../gbl/model/exam/ProctoringSettings.java | 4 +- .../seb/sebserver/gui/content/ExamForm.java | 24 ++++++ .../gui/content/ExamProctoringSettings.java | 52 +++++++++--- .../content/MonitoringClientConnection.java | 23 +++++- .../gui/content/action/ActionDefinition.java | 10 +++ .../gui/service/ResourceService.java | 3 +- .../api/exam/SaveProctoringSettings.java | 2 +- .../api/session/GetProctorURLForClient.java | 42 ++++++++++ .../ProctoringSettingsValidator.java | 81 +++++++++++++++++++ .../validation/ValidProctoringSettings.java | 32 ++++++++ .../api/ExamAdministrationController.java | 2 +- src/main/resources/messages.properties | 2 + 13 files changed, 261 insertions(+), 17 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetProctorURLForClient.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/validation/ProctoringSettingsValidator.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/validation/ValidProctoringSettings.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index c78c6993..94ac5708 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -126,7 +126,6 @@ public final class API { public static final String EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT = "/check-seb-restriction"; 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_PROCTOR_PATH_SEGMENT = "/proctoring"; public static final String EXAM_INDICATOR_ENDPOINT = "/indicator"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringSettings.java index 4881ea06..9397579e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringSettings.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringSettings.java @@ -18,8 +18,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Entity; +import ch.ethz.seb.sebserver.webservice.servicelayer.validation.ValidProctoringSettings; @JsonIgnoreProperties(ignoreUnknown = true) +@ValidProctoringSettings public class ProctoringSettings implements Entity { public enum ServerType { @@ -42,7 +44,7 @@ public class ProctoringSettings implements Entity { public final ServerType serverType; @JsonProperty(ATTR_SERVER_URL) - @URL(message = "examProctoring:serverURL:invalidURL") + @URL(message = "proctoringSettings:serverURL:invalidURL") public final String serverURL; @JsonProperty(ATTR_APP_KEY) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java index 39ea8f96..f14449fb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java @@ -33,6 +33,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain; 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.ExamStatus; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult.ErrorType; @@ -57,6 +58,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamConsistency; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckSEBRestriction; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam; +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.lmssetup.GetLmsSetup; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup; @@ -119,6 +121,7 @@ public class ExamForm implements TemplateComposer { private final PageService pageService; private final ResourceService resourceService; private final ExamSEBRestrictionSettings examSEBRestrictionSettings; + private final ExamProctoringSettings examProctoringSettings; private final WidgetFactory widgetFactory; private final RestService restService; private final ExamDeletePopup examDeletePopup; @@ -128,6 +131,7 @@ public class ExamForm implements TemplateComposer { protected ExamForm( final PageService pageService, final ExamSEBRestrictionSettings examSEBRestrictionSettings, + final ExamProctoringSettings examProctoringSettings, final ExamToConfigBindingForm examToConfigBindingForm, final DownloadService downloadService, final ExamDeletePopup examDeletePopup, @@ -137,6 +141,7 @@ public class ExamForm implements TemplateComposer { this.pageService = pageService; this.resourceService = pageService.getResourceService(); this.examSEBRestrictionSettings = examSEBRestrictionSettings; + this.examProctoringSettings = examProctoringSettings; this.widgetFactory = pageService.getWidgetFactory(); this.restService = this.resourceService.getRestService(); this.examDeletePopup = examDeletePopup; @@ -336,6 +341,13 @@ public class ExamForm implements TemplateComposer { ? this.restService.getRestCall(ImportAsExam.class) : this.restService.getRestCall(SaveExam.class)); + final boolean proctoringEnabled = this.restService + .getBuilder(GetProctoringSettings.class) + .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) + .call() + .map(ProctoringSettings::getEnableProctoring) + .getOr(false); + final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(formContext .clearEntityKeys() .removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA)); @@ -384,6 +396,18 @@ public class ExamForm implements TemplateComposer { .publishIf(() -> sebRestrictionAvailable && readonly && modifyGrant && !importFromQuizData && BooleanUtils.isTrue(isRestricted)) + .newAction(ActionDefinition.EXAM_PROCTORING_ON) + .withEntityKey(entityKey) + .withExec(this.examProctoringSettings.settingsFunction(this.pageService)) + .noEventPropagation() + .publishIf(() -> proctoringEnabled && readonly) + + .newAction(ActionDefinition.EXAM_PROCTORING_OFF) + .withEntityKey(entityKey) + .withExec(this.examProctoringSettings.settingsFunction(this.pageService)) + .noEventPropagation() + .publishIf(() -> !proctoringEnabled && readonly) + .newAction(ActionDefinition.EXAM_DELETE) .withEntityKey(entityKey) .withExec(this.examDeletePopup.deleteWizardFunction(pageContext)) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java index 3bdfbaa2..7f0c5caa 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java @@ -16,12 +16,16 @@ import org.apache.commons.lang3.BooleanUtils; 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.api.API; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType; +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; @@ -30,12 +34,16 @@ 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.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveProctoringSettings; +@Lazy +@Component +@GuiProfile public class ExamProctoringSettings { private static final Logger log = LoggerFactory.getLogger(ExamProctoringSettings.class); @@ -55,10 +63,6 @@ public class ExamProctoringSettings { private final static LocTextKey SEB_PROCTORING_FORM_SECRET = new LocTextKey("sebserver.exam.proctoring.form.secret"); - public ExamProctoringSettings() { - // TODO Auto-generated constructor stub - } - Function settingsFunction(final PageService pageService) { return action -> { @@ -75,7 +79,7 @@ public class ExamProctoringSettings { pageService, action.pageContext()); - final Predicate> doBind = formHandle -> doCreate( + final Predicate> doBind = formHandle -> doSaveSettings( pageService, pageContext, formHandle); @@ -90,7 +94,7 @@ public class ExamProctoringSettings { }; } - private boolean doCreate( + private boolean doSaveSettings( final PageService pageService, final PageContext pageContext, final FormHandle formHandle) { @@ -105,6 +109,8 @@ public class ExamProctoringSettings { ProctoringSettings examProctoring = null; try { final Form form = formHandle.getForm(); + form.clearErrors(); + final boolean enabled = BooleanUtils.toBoolean( form.getFieldValue(ProctoringSettings.ATTR_ENABLE_PROCTORING)); final ServerType serverType = ServerType.valueOf( @@ -126,7 +132,7 @@ public class ExamProctoringSettings { return false; } - return !pageService + final boolean saveOk = !pageService .getRestService() .getBuilder(SaveProctoringSettings.class) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) @@ -134,6 +140,19 @@ public class ExamProctoringSettings { .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 @@ -196,9 +215,24 @@ public class ExamProctoringSettings { ProctoringSettings.ATTR_SERVER_TYPE, SEB_PROCTORING_FORM_TYPE, proctoringSettings.serverType.name(), - this.pageService.getResourceService()::examProctoringTypeResources)) + resourceService::examProctoringTypeResources)) - // TODO + .addField(FormBuilder.text( + ProctoringSettings.ATTR_SERVER_URL, + SEB_PROCTORING_FORM_URL, + proctoringSettings.serverURL)) + + .addField(FormBuilder.text( + ProctoringSettings.ATTR_APP_KEY, + SEB_PROCTORING_FORM_APPKEY, + proctoringSettings.appKey)) + + .addField(FormBuilder.password( + ProctoringSettings.ATTR_APP_SECRET, + SEB_PROCTORING_FORM_SECRET, + (proctoringSettings.appSecret != null) + ? String.valueOf(proctoringSettings.appSecret) + : null)) .build(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java index 072069f7..a2c4018f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java @@ -24,6 +24,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain; 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.Indicator; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; 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; @@ -45,8 +46,10 @@ 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.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.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorURLForClient; 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.InstructionProcessor; @@ -237,6 +240,13 @@ public class MonitoringClientConnection implements TemplateComposer { .compose(pageContext.copyOf(content)); + final boolean proctoringEnabled = restService + .getBuilder(GetProctoringSettings.class) + .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId) + .call() + .map(ProctoringSettings::getEnableProctoring) + .getOr(false); + actionBuilder .newAction(ActionDefinition.MONITOR_EXAM_BACK_TO_OVERVIEW) .withEntityKey(parentEntityKey) @@ -256,15 +266,16 @@ public class MonitoringClientConnection implements TemplateComposer { connectionData.clientConnection.status == ConnectionStatus.ACTIVE) .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING) - .withExec(this::openProctorScreen) + .withEntityKey(parentEntityKey) + .withExec(action -> this.openProctorScreen(action, connectionToken)) .noEventPropagation() - .publish() + .publishIf(() -> proctoringEnabled) ; } - private PageAction openProctorScreen(final PageAction action) { + private PageAction openProctorScreen(final PageAction action, final String connectionToken) { // // final ProctorDialog dialog = new ProctorDialog(action.pageContext().getParent().getShell()); // dialog.open(EVENT_LIST_TITLE_KEY, @@ -274,6 +285,12 @@ public class MonitoringClientConnection implements TemplateComposer { // urlLauncher.openURL( // "https://seb-jitsi.ethz.ch/TestRoomABC?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0Ijp7InVzZXIiOnsiYXZhdGFyIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9qb2huLWRvZSIsIm5hbWUiOiJEaXNwbGF5IE5hbWUiLCJlbWFpbCI6Im5hbWVAZXhhbXBsZS5jb20ifX0sImF1ZCI6InNlYi1qaXRzaSIsImlzcyI6InNlYi1qaXRzaSIsInN1YiI6Im1lZXQuaml0c2kiLCJyb29tIjoiKiJ9.SD9Zs78mMFqxS1tpalPTykYYaubIYsj_406WAOhcqxQ"); + final String proctorURL = this.pageService.getRestService().getBuilder(GetProctorURLForClient.class) + .withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId) + .withURIVariable(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken) + .call() + .getOrThrow(); + final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class); javaScriptExecutor.execute( "window.open(" diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index a1cecd14..f516bd2f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -297,6 +297,16 @@ public enum ActionDefinition { ImageIcon.LOCK, PageStateDefinitionImpl.EXAM_VIEW, ActionCategory.FORM), + EXAM_PROCTORING_ON( + new LocTextKey("sebserver.exam.proctoring.actions.open"), + ImageIcon.VISIBILITY, + PageStateDefinitionImpl.EXAM_VIEW, + ActionCategory.FORM), + EXAM_PROCTORING_OFF( + new LocTextKey("sebserver.exam.proctoring.actions.open"), + ImageIcon.VISIBILITY_OFF, + PageStateDefinitionImpl.EXAM_VIEW, + ActionCategory.FORM), EXAM_CONFIGURATION_NEW( new LocTextKey("sebserver.exam.configuration.action.list.new"), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java index b40cde03..849a28d2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java @@ -39,6 +39,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction.PermissionComponent; import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction.WhiteListPath; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; @@ -372,7 +373,7 @@ public class ResourceService { } public List> examProctoringTypeResources() { - return Arrays.stream(ExamType.values()) + return Arrays.stream(ServerType.values()) .map(type -> new Tuple3<>( type.name(), this.i18nSupport.getText(EXAM_PROCTORING_TYPE_PREFIX + type.name()), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveProctoringSettings.java index 220917c7..f174dd9b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveProctoringSettings.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/exam/SaveProctoringSettings.java @@ -36,7 +36,7 @@ public class SaveProctoringSettings extends RestCall { MediaType.APPLICATION_JSON_UTF8, API.EXAM_ADMINISTRATION_ENDPOINT + API.MODEL_ID_VAR_PATH_SEGMENT - + API.EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT); + + API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetProctorURLForClient.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetProctorURLForClient.java new file mode 100644 index 00000000..15a929bb --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetProctorURLForClient.java @@ -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.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.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; + +@Lazy +@Component +@GuiProfile +public class GetProctorURLForClient extends RestCall { + + public GetProctorURLForClient() { + super(new TypeKey<>( + CallType.GET_SINGLE, + EntityType.EXAM_PROCTOR_DATA, + new TypeReference() { + }), + HttpMethod.GET, + MediaType.APPLICATION_JSON_UTF8, + API.EXAM_ADMINISTRATION_ENDPOINT + + API.MODEL_ID_VAR_PATH_SEGMENT + + API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT + + API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/validation/ProctoringSettingsValidator.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/validation/ProctoringSettingsValidator.java new file mode 100644 index 00000000..7b0c7f47 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/validation/ProctoringSettingsValidator.java @@ -0,0 +1,81 @@ +/* + * 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.webservice.servicelayer.validation; + +import java.net.InetAddress; +import java.net.URI; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +import org.apache.commons.lang3.StringUtils; + +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType; + +public class ProctoringSettingsValidator implements ConstraintValidator { + + @Override + public boolean isValid(final ProctoringSettings value, final ConstraintValidatorContext context) { + if (value == null) { + return false; + } + + if (value.enableProctoring) { + if (value.serverType == ServerType.JITSI_MEET) { + boolean passed = true; + if (StringUtils.isBlank(value.serverURL)) { + context.disableDefaultConstraintViolation(); + context + .buildConstraintViolationWithTemplate("proctoringSettings:serverURL:notNull") + .addPropertyNode("serverURL").addConstraintViolation(); + passed = false; + } + + try { + + if (!InetAddress.getByName(new URI(value.serverURL).getHost()).isReachable(5000)) { + context.disableDefaultConstraintViolation(); + context + .buildConstraintViolationWithTemplate("proctoringSettings:serverURL:serverNotAvailable") + .addPropertyNode("serverURL").addConstraintViolation(); + passed = false; + } + } catch (final Exception e) { + context.disableDefaultConstraintViolation(); + context + .buildConstraintViolationWithTemplate("proctoringSettings:serverURL:serverNotAvailable") + .addPropertyNode("serverURL").addConstraintViolation(); + passed = false; + } + + if (StringUtils.isBlank(value.appKey)) { + context.disableDefaultConstraintViolation(); + context + .buildConstraintViolationWithTemplate("proctoringSettings:appKey:notNull") + .addPropertyNode("appKey").addConstraintViolation(); + passed = false; + } + + if (StringUtils.isBlank(value.appSecret)) { + context.disableDefaultConstraintViolation(); + context + .buildConstraintViolationWithTemplate("proctoringSettings:appSecret:notNull") + .addPropertyNode("appSecret").addConstraintViolation(); + passed = false; + } + + return passed; + } + } + + return true; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/validation/ValidProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/validation/ValidProctoringSettings.java new file mode 100644 index 00000000..5d68b9a4 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/validation/ValidProctoringSettings.java @@ -0,0 +1,32 @@ +/* + * 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.webservice.servicelayer.validation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.Payload; + +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = ProctoringSettingsValidator.class) +@Documented +public @interface ValidProctoringSettings { + + String message() default "{mandatoryWhenEnabled}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java index bdc11849..45f8a46e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java @@ -429,7 +429,7 @@ public class ExamAdministrationController extends EntityController { + API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT + API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT, method = RequestMethod.GET, - produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + produces = MediaType.TEXT_PLAIN_VALUE) public String getExamProctoringURL( @RequestParam( name = API.PARAM_INSTITUTION_ID, diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 03e91493..fdcbeaba 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -90,6 +90,7 @@ sebserver.form.validation.fieldError.password.mismatch=The retyped password does sebserver.form.validation.fieldError.invalidURL=The input does not match the URL pattern. sebserver.form.validation.fieldError.exists=This name already exists. Please choose another one. sebserver.form.validation.fieldError.email=Invalid mail address +sebserver.form.validation.fieldError.serverNotAvailable=No service seems to be available within the given URL sebserver.error.unexpected=Unexpected Error sebserver.page.message=Information sebserver.dialog.confirm.title=Confirmation @@ -1396,6 +1397,7 @@ sebserver.monitoring.exam.connection.action.hide.disabled=Hide Canceled sebserver.monitoring.exam.connection.action.show.disabled=Show Canceled sebserver.monitoring.exam.connection.action.hide.undefined=Hide Undefined sebserver.monitoring.exam.connection.action.show.undefined=Show Undefined +sebserver.monitoring.exam.connection.action.proctoring=Proctoring sebserver.monitoring.exam.connection.eventlist.title=Events sebserver.monitoring.exam.connection.eventlist.title.tooltip=All events and logs sent by the SEB Client