diff --git a/pom.xml b/pom.xml index d7dcf41d..c670a1ed 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ jar - 1.1.0-rc1 + 1.2.0-SNAPSHOT ${sebserver-version} ${sebserver-version} UTF-8 diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SEBProctoringConnectionData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SEBProctoringConnection.java similarity index 94% rename from src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SEBProctoringConnectionData.java rename to src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SEBProctoringConnection.java index 1f6cd88a..f28cc761 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SEBProctoringConnectionData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/SEBProctoringConnection.java @@ -15,7 +15,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ProctoringServerType; @JsonIgnoreProperties(ignoreUnknown = true) -public class SEBProctoringConnectionData { +public class SEBProctoringConnection { public static final String ATTR_CONNECTION_TOKEN = "connectionToken"; public static final String ATTR_SERVER_HOST = "serverHost"; @@ -47,7 +47,7 @@ public class SEBProctoringConnectionData { public final String accessToken; @JsonCreator - public SEBProctoringConnectionData( + public SEBProctoringConnection( @JsonProperty(ProctoringSettings.ATTR_SERVER_TYPE) final ProctoringServerType proctoringServerType, @JsonProperty(ATTR_CONNECTION_TOKEN) final String connectionToken, @JsonProperty(ATTR_SERVER_HOST) final String serverHost, 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 0721d85f..8a927f1d 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 @@ -32,7 +32,7 @@ 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.exam.SEBProctoringConnectionData; +import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnection; 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; @@ -455,12 +455,12 @@ public class MonitoringClientConnection implements TemplateComposer { if (roomOptional.isPresent()) { final RemoteProctoringRoom room = roomOptional.get(); - final SEBProctoringConnectionData proctoringConnectionData = this.pageService + final SEBProctoringConnection proctoringConnectionData = this.pageService .getRestService() .getBuilder(GetProctorRoomConnectionData.class) .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId)) - .withQueryParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, room.name) - .withQueryParam(SEBProctoringConnectionData.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject)) + .withQueryParam(SEBProctoringConnection.ATTR_ROOM_NAME, room.name) + .withQueryParam(SEBProctoringConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject)) .call() .getOrThrow(); @@ -499,7 +499,7 @@ public class MonitoringClientConnection implements TemplateComposer { .getProctoringGUIService(); if (!proctoringGUIService.hasRoom(roomName)) { - final SEBProctoringConnectionData proctoringConnectionData = proctoringGUIService + final SEBProctoringConnection proctoringConnectionData = proctoringGUIService .registerNewSingleProcotringRoom( examId, roomName, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java index 9d81d9d9..31384816 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java @@ -44,7 +44,7 @@ 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.exam.SEBProctoringConnectionData; +import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; @@ -445,7 +445,7 @@ public class MonitoringRunningExam implements TemplateComposer { String activeAllRoomName = proctoringGUIService.getTownhallRoom(examId.modelId); if (activeAllRoomName == null) { - final SEBProctoringConnectionData proctoringConnectionData = proctoringGUIService + final SEBProctoringConnection proctoringConnectionData = proctoringGUIService .registerTownhallRoom( examId.modelId, this.pageService.getI18nSupport().getText(EXAM_ROOM_NAME)) @@ -642,12 +642,12 @@ public class MonitoringRunningExam implements TemplateComposer { final RemoteProctoringRoom room, final PageAction action) { - final SEBProctoringConnectionData proctoringConnectionData = this.pageService + final SEBProctoringConnection proctoringConnectionData = this.pageService .getRestService() .getBuilder(GetProctorRoomConnectionData.class) .withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId)) - .withQueryParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, room.name) - .withQueryParam(SEBProctoringConnectionData.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject)) + .withQueryParam(SEBProctoringConnection.ATTR_ROOM_NAME, room.name) + .withQueryParam(SEBProctoringConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject)) .call() .getOrThrow(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizLookupList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizLookupList.java index a6ae673e..a60278ae 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizLookupList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizLookupList.java @@ -115,9 +115,11 @@ public class QuizLookupList implements TemplateComposer { private final ResourceService resourceService; private final PageService pageService; private final int pageSize; + private final DateTime filterStartDate; protected QuizLookupList( final PageService pageService, + @Value("${sebserver.gui.filter.date.from.years:2}") final Integer startYearFromNow, @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { this.pageService = pageService; @@ -125,6 +127,10 @@ public class QuizLookupList implements TemplateComposer { this.resourceService = pageService.getResourceService(); this.pageSize = pageSize; + this.filterStartDate = Utils + .toDateTimeUTC(Utils.getMillisecondsNow()) + .minusYears(startYearFromNow); + this.institutionFilter = new TableFilterAttribute( CriteriaType.SINGLE_SELECTION, Entity.FILTER_ATTR_INSTITUTION, @@ -194,9 +200,7 @@ public class QuizLookupList implements TemplateComposer { .withFilter(new TableFilterAttribute( CriteriaType.DATE, QuizData.FILTER_ATTR_START_TIME, - Utils.toDateTimeUTC(Utils.getMillisecondsNow()) - .minusYears(1) - .toString())) + this.filterStartDate.toString())) .sortable()) .withColumn(new ColumnDefinition<>( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBExamConfigForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBExamConfigForm.java index 8dcdf923..7cdeaaad 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBExamConfigForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBExamConfigForm.java @@ -245,13 +245,6 @@ public class SEBExamConfigForm implements TemplateComposer { .noEventPropagation() .publishIf(() -> modifyGrant && isReadonly) -// // TODO shall this got to settings form? -// .newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_TO_EXISTING_CONFIG) -// .withEntityKey(entityKey) -// .withExec(this.sebExamConfigImportPopup.importFunction(false)) -// .noEventPropagation() -// .publishIf(() -> modifyGrant && isReadonly && !isAttachedToExam) - .newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_SAVE) .withEntityKey(entityKey) .withExec(formHandle::processFormSave) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBExamConfigImportPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBExamConfigImportPopup.java index a09c1bc7..1b52f458 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBExamConfigImportPopup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBExamConfigImportPopup.java @@ -104,133 +104,92 @@ public class SEBExamConfigImportPopup { final Control fieldControl = form.getFieldInput(API.IMPORT_FILE_ATTR_NAME); final PageContext context = formHandle.getContext(); - // Ad-hoc field validation - if (newConfig) { - formHandle.process(name -> true, Form.FormFieldAccessor::resetError); - final String fieldValue = form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME); - if (StringUtils.isBlank(fieldValue)) { - form.setFieldError( - Domain.CONFIGURATION_NODE.ATTR_NAME, - this.pageService - .getI18nSupport() - .getText(new LocTextKey("sebserver.form.validation.fieldError.notNull"))); - return false; - } else if (fieldValue.length() < 3 || fieldValue.length() > 255) { - form.setFieldError( - Domain.CONFIGURATION_NODE.ATTR_NAME, - this.pageService - .getI18nSupport() - .getText(new LocTextKey("sebserver.form.validation.fieldError.size", - null, - null, - null, - 3, - 255))); - return false; - } else { - // check if name already exists - try { - if (this.pageService.getRestService() - .getBuilder(GetExamConfigNodeNames.class) - .call() - .getOrThrow() - .stream() - .filter(n -> n.name.equals(fieldValue)) - .findFirst() - .isPresent()) { - - form.setFieldError( - Domain.CONFIGURATION_NODE.ATTR_NAME, - this.pageService - .getI18nSupport() - .getText(new LocTextKey( - "sebserver.form.validation.fieldError.name.notunique"))); - return false; - } - } catch (final Exception e) { - log.error("Failed to verify unique name: {}", e.getMessage()); - } - } + if (!(fieldControl instanceof FileUploadSelection)) { + return false; } - if (fieldControl instanceof FileUploadSelection) { - final FileUploadSelection fileUpload = (FileUploadSelection) fieldControl; - final InputStream inputStream = fileUpload.getInputStream(); - if (inputStream != null) { - final RestCall.RestCallBuilder restCall = (newConfig) - ? this.pageService.getRestService() - .getBuilder(ImportNewExamConfig.class) - : this.pageService.getRestService() - .getBuilder(ImportExamConfigOnExistingConfig.class); + if (!checkInput(formHandle, newConfig, form)) { + return false; + } + final FileUploadSelection fileUpload = (FileUploadSelection) fieldControl; + final InputStream inputStream = fileUpload.getInputStream(); + if (inputStream != null) { + final RestCall.RestCallBuilder restCall = (newConfig) + ? this.pageService.getRestService() + .getBuilder(ImportNewExamConfig.class) + : this.pageService.getRestService() + .getBuilder(ImportExamConfigOnExistingConfig.class); + + restCall + .withHeader( + API.IMPORT_PASSWORD_ATTR_NAME, + form.getFieldValue(API.IMPORT_PASSWORD_ATTR_NAME)) + .withBody(inputStream); + + if (newConfig) { restCall .withHeader( - API.IMPORT_PASSWORD_ATTR_NAME, - form.getFieldValue(API.IMPORT_PASSWORD_ATTR_NAME)) - .withBody(inputStream); + Domain.CONFIGURATION_NODE.ATTR_NAME, + form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME)) + .withHeader( + Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, + form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION)) + .withHeader( + Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID, + form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID)); + } else { + restCall.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId); + } - if (newConfig) { - restCall - .withHeader( - Domain.CONFIGURATION_NODE.ATTR_NAME, - form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME)) - .withHeader( - Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, - form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION)) - .withHeader( - Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID, - form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID)); - } else { - restCall.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId); - } + final Result configuration = restCall + .call(); - final Result configuration = restCall - .call(); - - if (!configuration.hasError()) { - context.publishInfo(SEBExamConfigForm.FORM_IMPORT_CONFIRM_TEXT_KEY); - if (newConfig) { - - final PageAction action = this.pageService.pageActionBuilder(context) + if (!configuration.hasError()) { + context.publishInfo(SEBExamConfigForm.FORM_IMPORT_CONFIRM_TEXT_KEY); + final PageAction action = (newConfig) + ? this.pageService.pageActionBuilder(context) .newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_TO_NEW_CONFIG) + .create() + : this.pageService.pageActionBuilder(context) + .newAction(ActionDefinition.SEB_EXAM_CONFIG_MODIFY) .create(); - this.pageService.firePageEvent( - new ActionEvent(action), - action.pageContext()); - } - } else { - final Exception error = configuration.getError(); - if (error instanceof RestCallError) { - ((RestCallError) error) - .getErrorMessages() - .stream() - .findFirst() - .ifPresent(message -> { - if (APIMessage.ErrorMessage.MISSING_PASSWORD.isOf(message)) { - formHandle - .getContext() - .publishPageMessage(MISSING_PASSWORD); - } else { - formHandle - .getContext() - .notifyImportError(EntityType.CONFIGURATION_NODE, error); - } - }); - return true; - } + this.pageService.firePageEvent( + new ActionEvent(action), + action.pageContext()); - formHandle.getContext().notifyError( - SEBExamConfigForm.FORM_TITLE, - configuration.getError()); - - } - return true; } else { - formHandle.getContext().publishPageMessage( - new LocTextKey("sebserver.error.unexpected"), - new LocTextKey("Please select a valid SEB Exam Configuration File")); + final Exception error = configuration.getError(); + if (error instanceof RestCallError) { + ((RestCallError) error) + .getErrorMessages() + .stream() + .findFirst() + .ifPresent(message -> { + if (APIMessage.ErrorMessage.MISSING_PASSWORD.isOf(message)) { + formHandle + .getContext() + .publishPageMessage(MISSING_PASSWORD); + } else { + formHandle + .getContext() + .notifyImportError(EntityType.CONFIGURATION_NODE, error); + } + }); + return true; + } + + formHandle.getContext().notifyError( + SEBExamConfigForm.FORM_TITLE, + configuration.getError()); + } + return true; + } else { + formHandle.getContext().publishPageMessage( + new LocTextKey("sebserver.error.unexpected"), + new LocTextKey("Please select a valid SEB Exam Configuration File")); } return false; @@ -240,6 +199,58 @@ public class SEBExamConfigImportPopup { } } + private boolean checkInput(final FormHandle formHandle, final boolean newConfig, + final Form form) { + if (newConfig) { + formHandle.process(name -> true, Form.FormFieldAccessor::resetError); + final String fieldValue = form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME); + if (StringUtils.isBlank(fieldValue)) { + form.setFieldError( + Domain.CONFIGURATION_NODE.ATTR_NAME, + this.pageService + .getI18nSupport() + .getText(new LocTextKey("sebserver.form.validation.fieldError.notNull"))); + return false; + } else if (fieldValue.length() < 3 || fieldValue.length() > 255) { + form.setFieldError( + Domain.CONFIGURATION_NODE.ATTR_NAME, + this.pageService + .getI18nSupport() + .getText(new LocTextKey("sebserver.form.validation.fieldError.size", + null, + null, + null, + 3, + 255))); + return false; + } else { + // check if name already exists + try { + if (this.pageService.getRestService() + .getBuilder(GetExamConfigNodeNames.class) + .call() + .getOrThrow() + .stream() + .filter(n -> n.name.equals(fieldValue)) + .findFirst() + .isPresent()) { + + form.setFieldError( + Domain.CONFIGURATION_NODE.ATTR_NAME, + this.pageService + .getI18nSupport() + .getText(new LocTextKey( + "sebserver.form.validation.fieldError.name.notunique"))); + return false; + } + } catch (final Exception e) { + log.error("Failed to verify unique name: {}", e.getMessage()); + } + } + } + return true; + } + private final class ImportFormContext implements ModalInputDialogComposer> { private final PageService pageService; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBSettingsForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBSettingsForm.java index 6070ea7f..f4c2f96f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBSettingsForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBSettingsForm.java @@ -255,7 +255,6 @@ public class SEBSettingsForm implements TemplateComposer { .newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_TO_EXISTING_CONFIG) .withEntityKey(entityKey) .withExec(this.sebExamConfigImportPopup.importFunction(false)) - .noEventPropagation() .publishIf(() -> examConfigGrant.iw() && !readonly && !isAttachedToExam) .newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/BrowserViewModeRule.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/BrowserViewModeRule.java index 9f8d3e5c..8d7686f5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/BrowserViewModeRule.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/BrowserViewModeRule.java @@ -56,7 +56,9 @@ public class BrowserViewModeRule implements ValueChangeRule { if (KEY_TOUCH_OPTIMIZED.equals(attribute.name)) { if (BooleanUtils.toBoolean(value.value)) { context.disableGroup(KEY_MAIN_WINDOW_GROUP); - context.setValue(KEY_BROWSER_VIEW_MODE, "2"); + context.setValue( + KEY_BROWSER_VIEW_MODE, + context.getAttributeByName(KEY_BROWSER_VIEW_MODE).defaultValue); } else { context.setValue(KEY_TOUCH_EXIT, Constants.FALSE_STRING); context.disable(KEY_TOUCH_EXIT); @@ -70,7 +72,9 @@ public class BrowserViewModeRule implements ValueChangeRule { case 1: { context.disable(KEY_TOUCH_EXIT); context.disableGroup(KEY_MAIN_WINDOW_GROUP); - context.setValue(KEY_TOUCH_OPTIMIZED, Constants.FALSE_STRING); + context.setValue( + KEY_TOUCH_OPTIMIZED, + context.getAttributeByName(KEY_TOUCH_OPTIMIZED).defaultValue); break; } case 2: { @@ -79,7 +83,9 @@ public class BrowserViewModeRule implements ValueChangeRule { } default: { context.disable(KEY_TOUCH_EXIT); - context.setValue(KEY_TOUCH_OPTIMIZED, Constants.FALSE_STRING); + context.setValue( + KEY_TOUCH_OPTIMIZED, + context.getAttributeByName(KEY_TOUCH_OPTIMIZED).defaultValue); break; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/IgnoreSEBService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/IgnoreSEBService.java new file mode 100644 index 00000000..e28f0b8e --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/rules/IgnoreSEBService.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 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.examconfig.impl.rules; + +import org.apache.commons.lang3.BooleanUtils; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.examconfig.ValueChangeRule; +import ch.ethz.seb.sebserver.gui.service.examconfig.impl.ViewContext; + +@Lazy +@Service +@GuiProfile +public class IgnoreSEBService implements ValueChangeRule { + + public static final String KEY_IGNORE_SEB_SERVICE = "sebServiceIgnore"; + + public static final String KEY_SEB_SERVICE_POLICY = "sebServicePolicy"; + public static final String KEY_ATTR_1 = "enableWindowsUpdate"; + public static final String KEY_ATTR_2 = "enableChromeNotifications"; + public static final String KEY_ATTR_3 = "allowScreenSharing"; + + @Override + public boolean observesAttribute(final ConfigurationAttribute attribute) { + return KEY_IGNORE_SEB_SERVICE.equals(attribute.name); + } + + @Override + public void applyRule( + final ViewContext context, + final ConfigurationAttribute attribute, + final ConfigurationValue value) { + + if (KEY_IGNORE_SEB_SERVICE.equals(attribute.name)) { + if (BooleanUtils.toBoolean(value.value)) { + context.disable(KEY_SEB_SERVICE_POLICY); + context.disable(KEY_ATTR_1); + context.disable(KEY_ATTR_2); + context.disable(KEY_ATTR_3); + + context.setValue( + KEY_SEB_SERVICE_POLICY, + context.getAttributeByName(KEY_SEB_SERVICE_POLICY).defaultValue); + context.setValue( + KEY_ATTR_1, + context.getAttributeByName(KEY_ATTR_1).defaultValue); + context.setValue( + KEY_ATTR_2, + context.getAttributeByName(KEY_ATTR_2).defaultValue); + context.setValue( + KEY_ATTR_3, + context.getAttributeByName(KEY_ATTR_3).defaultValue); + } else { + context.enable(KEY_SEB_SERVICE_POLICY); + context.enable(KEY_ATTR_1); + context.enable(KEY_ATTR_2); + context.enable(KEY_ATTR_3); + } + } + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/ActivateTownhallRoom.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/ActivateTownhallRoom.java index 5e6852b8..93d8832b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/ActivateTownhallRoom.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/ActivateTownhallRoom.java @@ -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.EntityType; -import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; +import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnection; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; @Lazy @Component @GuiProfile -public class ActivateTownhallRoom extends RestCall { +public class ActivateTownhallRoom extends RestCall { public ActivateTownhallRoom() { super(new TypeKey<>( CallType.UNDEFINED, EntityType.EXAM_PROCTOR_DATA, - new TypeReference() { + new TypeReference() { }), HttpMethod.POST, MediaType.APPLICATION_FORM_URLENCODED, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetProctorRoomConnectionData.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetProctorRoomConnectionData.java index 960767b3..932d0f7b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetProctorRoomConnectionData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetProctorRoomConnectionData.java @@ -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.EntityType; -import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; +import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnection; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; @Lazy @Component @GuiProfile -public class GetProctorRoomConnectionData extends RestCall { +public class GetProctorRoomConnectionData extends RestCall { public GetProctorRoomConnectionData() { super(new TypeKey<>( CallType.GET_SINGLE, EntityType.EXAM_PROCTOR_DATA, - new TypeReference() { + new TypeReference() { }), HttpMethod.GET, MediaType.APPLICATION_FORM_URLENCODED, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/SendJoinRemoteProctoringRoom.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/SendJoinRemoteProctoringRoom.java index 5e8fe9fc..50622ed6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/SendJoinRemoteProctoringRoom.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/SendJoinRemoteProctoringRoom.java @@ -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.EntityType; -import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; +import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnection; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; @Lazy @Component @GuiProfile -public class SendJoinRemoteProctoringRoom extends RestCall { +public class SendJoinRemoteProctoringRoom extends RestCall { public SendJoinRemoteProctoringRoom() { super(new TypeKey<>( CallType.UNDEFINED, EntityType.EXAM_PROCTOR_DATA, - new TypeReference() { + new TypeReference() { }), HttpMethod.POST, MediaType.APPLICATION_FORM_URLENCODED, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ProctoringGUIService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ProctoringGUIService.java index 0e449d2f..de5eb00b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ProctoringGUIService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ProctoringGUIService.java @@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.model.Domain; -import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; +import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnection; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.ActivateTownhallRoom; @@ -82,7 +82,7 @@ public class ProctoringGUIService { public static void setCurrentProctoringWindowData( final String examId, - final SEBProctoringConnectionData data) { + final SEBProctoringConnection data) { RWT.getUISession().getHttpSession().setAttribute( SESSION_ATTR_PROCTORING_DATA, @@ -103,7 +103,7 @@ public class ProctoringGUIService { .orElseGet(() -> null); } - public Result registerNewSingleProcotringRoom( + public Result registerNewSingleProcotringRoom( final String examId, final String roomName, final String subject, @@ -111,8 +111,8 @@ public class ProctoringGUIService { return this.restService.getBuilder(SendJoinRemoteProctoringRoom.class) .withURIVariable(API.PARAM_MODEL_ID, examId) - .withFormParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, roomName) - .withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject) + .withFormParam(SEBProctoringConnection.ATTR_ROOM_NAME, roomName) + .withFormParam(SEBProctoringConnection.ATTR_SUBJECT, subject) .withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken) .call() .map(connection -> { @@ -122,13 +122,13 @@ public class ProctoringGUIService { }); } - public Result registerTownhallRoom( + public Result registerTownhallRoom( final String examId, final String subject) { return this.restService.getBuilder(ActivateTownhallRoom.class) .withURIVariable(API.PARAM_MODEL_ID, examId) - .withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject) + .withFormParam(SEBProctoringConnection.ATTR_SUBJECT, subject) .call() .map(connection -> { this.rooms.put( @@ -139,7 +139,7 @@ public class ProctoringGUIService { }); } - public Result registerNewProcotringRoom( + public Result registerNewProcotringRoom( final String examId, final String roomName, final String subject, @@ -147,8 +147,8 @@ public class ProctoringGUIService { return this.restService.getBuilder(SendJoinRemoteProctoringRoom.class) .withURIVariable(API.PARAM_MODEL_ID, examId) - .withFormParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, roomName) - .withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject) + .withFormParam(SEBProctoringConnection.ATTR_ROOM_NAME, roomName) + .withFormParam(SEBProctoringConnection.ATTR_SUBJECT, subject) .withFormParam( API.EXAM_API_SEB_CONNECTION_TOKEN, StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR_CHAR)) @@ -172,7 +172,7 @@ public class ProctoringGUIService { } this.restService.getBuilder(SendJoinRemoteProctoringRoom.class) .withURIVariable(API.PARAM_MODEL_ID, examId) - .withFormParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, room) + .withFormParam(SEBProctoringConnection.ATTR_ROOM_NAME, room) .withFormParam( API.EXAM_API_SEB_CONNECTION_TOKEN, StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR_CHAR)) @@ -288,11 +288,11 @@ public class ProctoringGUIService { public static class ProctoringWindowData { public final String examId; - public final SEBProctoringConnectionData connectionData; + public final SEBProctoringConnection connectionData; protected ProctoringWindowData( final String examId, - final SEBProctoringConnectionData connectionData) { + final SEBProctoringConnection connectionData) { this.examId = examId; this.connectionData = connectionData; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java index 206c9a78..ccc8a806 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/edx/OpenEdxLmsAPITemplate.java @@ -28,6 +28,7 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; +import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; final class OpenEdxLmsAPITemplate implements LmsAPITemplate { @@ -65,7 +66,11 @@ final class OpenEdxLmsAPITemplate implements LmsAPITemplate { @Override public Result> getQuizzes(final FilterMap filterMap) { - return this.openEdxCourseAccess.getQuizzes(filterMap); + return this.openEdxCourseAccess + .getQuizzes(filterMap) + .map(quizzes -> quizzes.stream() + .filter(LmsAPIService.quizFilterPredicate(filterMap)) + .collect(Collectors.toList())); } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java index ef71a33d..8cd903bc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/moodle/MoodleLmsAPITemplate.java @@ -28,6 +28,7 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; +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.impl.NoSEBRestrictionException; @@ -67,7 +68,11 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate { @Override public Result> getQuizzes(final FilterMap filterMap) { - return this.moodleCourseAccess.getQuizzes(filterMap); + return this.moodleCourseAccess + .getQuizzes(filterMap) + .map(quizzes -> quizzes.stream() + .filter(LmsAPIService.quizFilterPredicate(filterMap)) + .collect(Collectors.toList())); } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringService.java index aa9fcb60..965bc52a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringService.java @@ -10,46 +10,51 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ProctoringServerType; -import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; +import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.util.Result; public interface ExamProctoringService { + /** Get the proctoring server type of the specific implementation + * + * @return the proctoring service type of the specific implementation */ ProctoringServerType getType(); + /** Use this to test the proctoring service settings against the remote proctoring server. + * + * @param examProctoring the settings to test + * @return Result refer to true if the settings are correct and the proctoring server can be accessed. */ Result testExamProctoring(final ProctoringSettings examProctoring); - Result createProctorPublicRoomConnection( + /** Used to get the proctor's room connection data. + * + * @param proctoringSettings the proctoring settings + * @param roomName the name of the room + * @param subject name of the room + * @return SEBProctoringConnectionData that contains all connection data */ + Result createProctorPublicRoomConnection( final ProctoringSettings proctoringSettings, final String roomName, final String subject); - Result getClientExamCollectingRoomConnectionData( - final ProctoringSettings proctoringSettings, - final String connectionToken); - - Result getClientExamCollectingRoomConnectionData( + Result getClientExamCollectingRoomConnection( final ProctoringSettings proctoringSettings, final ClientConnection connection); - Result getClientExamCollectingRoomConnectionData( + Result getClientExamCollectingRoomConnection( final ProctoringSettings proctoringSettings, final String connectionToken, final String roomName, final String subject); - Result getClientRoomConnectionData( - final ProctoringSettings proctoringSettings, - final String connectionToken); - - Result getClientRoomConnectionData( + Result getClientRoomConnection( final ProctoringSettings examProctoring, final String connectionToken, final String roomName, final String subject); - Result createProctoringConnectionData( + Result createProctoringConnection( final ProctoringServerType proctoringServerType, final String connectionToken, final String url, @@ -59,7 +64,8 @@ public interface ExamProctoringService { final String clientKey, final String roomName, final String subject, - final Long expTime); + final Long expTime, + final boolean moderator); Result createClientAccessToken( final ProctoringSettings proctoringSettings, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamJITSIProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamJITSIProctoringService.java index e58eac80..63b21282 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamJITSIProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamJITSIProctoringService.java @@ -25,7 +25,7 @@ import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ProctoringServerType; -import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; +import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; @@ -46,7 +46,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService { "{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; private static final String JITSI_ACCESS_TOKEN_PAYLOAD = - "{\"context\":{\"user\":{\"name\":\"%s\"}},\"iss\":\"%s\",\"aud\":\"%s\",\"sub\":\"%s\",\"room\":\"%s\"%s}"; + "{\"context\":{\"user\":{\"name\":\"%s\"}},\"iss\":\"%s\",\"aud\":\"%s\",\"sub\":\"%s\",\"room\":\"%s\"%s%s}"; private final RemoteProctoringRoomDAO remoteProctoringRoomDAO; private final AuthorizationService authorizationService; @@ -77,13 +77,13 @@ public class ExamJITSIProctoringService implements ExamProctoringService { } @Override - public Result createProctorPublicRoomConnection( + public Result createProctorPublicRoomConnection( final ProctoringSettings proctoringSettings, final String roomName, final String subject) { return Result.tryCatch(() -> { - return createProctoringConnectionData( + return createProctoringConnection( proctoringSettings.serverType, null, proctoringSettings.serverURL, @@ -93,25 +93,14 @@ public class ExamJITSIProctoringService implements ExamProctoringService { "seb-server", roomName, subject, - forExam(proctoringSettings)) + forExam(proctoringSettings), + true) .getOrThrow(); }); } @Override - public Result getClientExamCollectingRoomConnectionData( - final ProctoringSettings proctoringSettings, - final String connectionToken) { - - return this.examSessionService - .getConnectionData(connectionToken) - .flatMap(connection -> getClientExamCollectingRoomConnectionData( - proctoringSettings, - connection.clientConnection)); - } - - @Override - public Result getClientExamCollectingRoomConnectionData( + public Result getClientExamCollectingRoomConnection( final ProctoringSettings proctoringSettings, final ClientConnection connection) { @@ -121,7 +110,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService { .getRoomName(connection.getRemoteProctoringRoomId()) .getOrThrow(); - return createProctoringConnectionData( + return createProctoringConnection( proctoringSettings.serverType, null, proctoringSettings.serverURL, @@ -131,13 +120,14 @@ public class ExamJITSIProctoringService implements ExamProctoringService { "seb-client", roomName, connection.userSessionId, - forExam(proctoringSettings)) + forExam(proctoringSettings), + false) .getOrThrow(); }); } @Override - public Result getClientExamCollectingRoomConnectionData( + public Result getClientExamCollectingRoomConnection( final ProctoringSettings proctoringSettings, final String connectionToken, final String roomName, @@ -148,7 +138,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService { .getConnectionData(connectionToken) .getOrThrow(); - return createProctoringConnectionData( + return createProctoringConnection( proctoringSettings.serverType, null, proctoringSettings.serverURL, @@ -158,35 +148,14 @@ public class ExamJITSIProctoringService implements ExamProctoringService { "seb-client", roomName, subject, - forExam(proctoringSettings)) + forExam(proctoringSettings), + false) .getOrThrow(); }); } @Override - public Result getClientRoomConnectionData( - final ProctoringSettings proctoringSettings, - final String connectionToken) { - - return Result.tryCatch(() -> this.examSessionService - .getConnectionData(connectionToken) - .getOrThrow() - - ).flatMap(clientConnection -> { - final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding(); - final String roomName = urlEncoder.encodeToString( - Utils.toByteArray(clientConnection.clientConnection.connectionToken)); - - return getClientRoomConnectionData( - proctoringSettings, - connectionToken, - roomName, - clientConnection.clientConnection.userSessionId); - }); - } - - @Override - public Result getClientRoomConnectionData( + public Result getClientRoomConnection( final ProctoringSettings proctoringSettings, final String connectionToken, final String roomName, @@ -199,7 +168,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService { .getConnectionData(connectionToken) .getOrThrow(); - return createProctoringConnectionData( + return createProctoringConnection( proctoringSettings.serverType, connectionToken, proctoringSettings.serverURL, @@ -209,14 +178,15 @@ public class ExamJITSIProctoringService implements ExamProctoringService { "seb-client", roomName, subject, - expTime) + expTime, + false) .getOrThrow(); }); } @Override - public Result createProctoringConnectionData( + public Result createProctoringConnection( final ProctoringServerType proctoringServerType, final String connectionToken, final String url, @@ -226,7 +196,8 @@ public class ExamJITSIProctoringService implements ExamProctoringService { final String clientKey, final String roomName, final String subject, - final Long expTime) { + final Long expTime, + final boolean moderator) { return Result.tryCatch(() -> { @@ -242,9 +213,10 @@ public class ExamJITSIProctoringService implements ExamProctoringService { clientKey, roomName, expTime, - host); + host, + moderator); - return new SEBProctoringConnectionData( + return new SEBProctoringConnection( proctoringServerType, connectionToken, host, @@ -279,34 +251,27 @@ public class ExamJITSIProctoringService implements ExamProctoringService { "seb-client", roomName, forExam(proctoringSettings), - host); + host, + false); }); } - private String internalCreateAccessToken( + protected String internalCreateAccessToken( final String appKey, final CharSequence appSecret, final String clientName, final String clientKey, final String roomName, final Long expTime, - final String host) throws NoSuchAlgorithmException, InvalidKeyException { + final String host, + final boolean moderator) throws NoSuchAlgorithmException, InvalidKeyException { final StringBuilder builder = new StringBuilder(); final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding(); final String jwtHeaderPart = urlEncoder .encodeToString(JITSI_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8)); - final String jwtPayload = String.format( - JITSI_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""), - clientName, - appKey, - clientKey, - host, - roomName, - (expTime != null) - ? String.format(",\"exp\":%s", String.valueOf(expTime)) - : ""); + final String jwtPayload = createPayload(appKey, clientName, clientKey, roomName, expTime, host, moderator); final String jwtPayloadPart = urlEncoder .encodeToString(jwtPayload.getBytes(StandardCharsets.UTF_8)); final String message = jwtHeaderPart + "." + jwtPayloadPart; @@ -324,6 +289,31 @@ public class ExamJITSIProctoringService implements ExamProctoringService { return builder.toString(); } + protected String createPayload( + final String appKey, + final String clientName, + final String clientKey, + final String roomName, + final Long expTime, + final String host, + final boolean moderator) { + + final String jwtPayload = String.format( + JITSI_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""), + clientName, + appKey, + clientKey, + host, + roomName, + (moderator) + ? ",\"moderator\":true" + : ",\"moderator\":false", + (expTime != null) + ? String.format(",\"exp\":%s", String.valueOf(expTime)) + : ""); + return jwtPayload; + } + private long forExam(final ProctoringSettings examProctoring) { if (examProctoring.examId == null) { throw new IllegalStateException("Missing exam identifier from ExamProctoring data"); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamProctoringRoomServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamProctoringRoomServiceImpl.java index 3ee5c152..3c90f48f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamProctoringRoomServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamProctoringRoomServiceImpl.java @@ -20,7 +20,7 @@ import org.springframework.stereotype.Service; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ProctoringServerType; -import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; +import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType; @@ -196,9 +196,9 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getExamProctoring(examId) .getOrThrow(); - final SEBProctoringConnectionData proctoringData = + final SEBProctoringConnection proctoringData = this.examAdminService.getExamProctoringService(proctoringSettings.serverType) - .flatMap(s -> s.getClientExamCollectingRoomConnectionData( + .flatMap(s -> s.getClientExamCollectingRoomConnection( proctoringSettings, connectionToken, roomName, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java index 7464140d..47caaa92 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java @@ -9,6 +9,8 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; import java.util.Arrays; +import java.util.Base64; +import java.util.Base64.Encoder; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -31,13 +33,14 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; -import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; +import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType; import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; @@ -107,15 +110,16 @@ public class ExamProctoringController { path = API.MODEL_ID_VAR_PATH_SEGMENT, method = RequestMethod.GET, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) - public SEBProctoringConnectionData getProctorRoomData( + public SEBProctoringConnection getProctorRoomData( @RequestParam( name = API.PARAM_INSTITUTION_ID, required = true, defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, @PathVariable(name = API.PARAM_MODEL_ID) final Long examId, - @RequestParam(name = SEBProctoringConnectionData.ATTR_ROOM_NAME, required = true) final String roomName, - @RequestParam(name = SEBProctoringConnectionData.ATTR_SUBJECT, required = false) final String subject) { + @RequestParam(name = SEBProctoringConnection.ATTR_ROOM_NAME, required = true) final String roomName, + @RequestParam(name = SEBProctoringConnection.ATTR_SUBJECT, required = false) final String subject) { checkAccess(institutionId, examId); @@ -217,27 +221,12 @@ public class ExamProctoringController { checkAccess(institutionId, examId); - final ProctoringSettings settings = this.examSessionService + final ProctoringSettings proctoringSettings = this.examSessionService .getRunningExam(examId) .flatMap(this.examAdminService::getExamProctoring) .getOrThrow(); - final ExamProctoringService examProctoringService = this.examAdminService - .getExamProctoringService(settings.serverType) - .getOrThrow(); - - Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR)) - .stream() - .forEach(connectionToken -> { - examProctoringService - .getClientExamCollectingRoomConnectionData( - settings, - connectionToken) - .flatMap(data -> this.sendJoinInstruction(examId, connectionToken, data)) - .onError(error -> log.error("Failed to send rejoin for: {} cause: {}", - connectionToken, - error.getMessage())); - }); + sendJoinInstructions(connectionTokens, proctoringSettings); } @RequestMapping( @@ -245,17 +234,17 @@ public class ExamProctoringController { + API.EXAM_PROCTORING_JOIN_ROOM_PATH_SEGMENT, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public SEBProctoringConnectionData sendJoinProctoringRoomToClients( + public SEBProctoringConnection sendJoinProctoringRoomToClients( @RequestParam( name = API.PARAM_INSTITUTION_ID, required = true, defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, @PathVariable(name = API.PARAM_MODEL_ID) final Long examId, @RequestParam( - name = SEBProctoringConnectionData.ATTR_ROOM_NAME, + name = SEBProctoringConnection.ATTR_ROOM_NAME, required = true) final String roomName, @RequestParam( - name = SEBProctoringConnectionData.ATTR_SUBJECT, + name = SEBProctoringConnection.ATTR_SUBJECT, required = false) final String subject, @RequestParam( name = API.EXAM_API_SEB_CONNECTION_TOKEN, @@ -273,39 +262,30 @@ public class ExamProctoringController { .getOrThrow(); if (StringUtils.isNotBlank(connectionTokens)) { - final boolean single = connectionTokens.contains(Constants.LIST_SEPARATOR); - (single - ? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR)) - : Arrays.asList(connectionTokens)) - .stream() - .forEach(connectionToken -> { - final SEBProctoringConnectionData data = (single) - ? examProctoringService - .getClientRoomConnectionData(settings, connectionToken) - .onError(error -> log.error( - "Failed to get client room connection data for {} cause: {}", - connectionToken, - error.getMessage())) - .get() - : examProctoringService - .getClientRoomConnectionData( - settings, - connectionToken, - roomName, - (StringUtils.isNotBlank(subject)) ? subject : roomName) - .onError(error -> log.error( - "Failed to get client room connection data for {} cause: {}", - connectionToken, - error.getMessage())) - .get(); - if (data != null) { - sendJoinInstruction(examId, connectionToken, data) - .onError(error -> log.error( - "Failed to send proctoring leave instruction to client: {} cause: {}", - connectionToken, - error.getMessage())); - } - }); + + Arrays.asList(connectionTokens.split(Constants.LIST_SEPARATOR)) + .stream() + .forEach(connectionToken -> { + final SEBProctoringConnection proctoringConnection = + examProctoringService + .getClientRoomConnection( + settings, + connectionToken, + verifyRoomName(roomName, connectionToken), + (StringUtils.isNotBlank(subject)) ? subject : roomName) + .onError(error -> log.error( + "Failed to get client room connection data for {} cause: {}", + connectionToken, + error.getMessage())) + .get(); + if (proctoringConnection != null) { + sendJoinInstruction(settings.examId, connectionToken, proctoringConnection) + .onError(error -> log.error( + "Failed to send proctoring leave instruction to client: {} cause: {}", + connectionToken, + error.getMessage())); + } + }); } return examProctoringService.createProctorPublicRoomConnection( @@ -315,6 +295,16 @@ public class ExamProctoringController { .getOrThrow(); } + private String verifyRoomName(final String requestedRoomName, final String connectionToken) { + if (StringUtils.isNotBlank(requestedRoomName)) { + return requestedRoomName; + } + + final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding(); + return urlEncoder.encodeToString( + Utils.toByteArray(connectionToken)); + } + @RequestMapping( path = API.MODEL_ID_VAR_PATH_SEGMENT + API.EXAM_PROCTORING_TOWNHALL_ROOM_DATA, @@ -338,14 +328,14 @@ public class ExamProctoringController { + API.EXAM_PROCTORING_ACTIVATE_TOWNHALL_ROOM, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public SEBProctoringConnectionData activateTownhall( + public SEBProctoringConnection activateTownhall( @RequestParam( name = API.PARAM_INSTITUTION_ID, required = true, defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, @PathVariable(name = API.PARAM_MODEL_ID) final Long examId, @RequestParam( - name = SEBProctoringConnectionData.ATTR_SUBJECT, + name = SEBProctoringConnection.ATTR_SUBJECT, required = false) final String subject) { checkAccess(institutionId, examId); @@ -371,8 +361,8 @@ public class ExamProctoringController { .getOrThrow() .stream() .forEach(cc -> { - final SEBProctoringConnectionData data = examProctoringService - .getClientRoomConnectionData( + final SEBProctoringConnection data = examProctoringService + .getClientRoomConnection( settings, cc.clientConnection.connectionToken, townhallRoom.name, @@ -431,7 +421,7 @@ public class ExamProctoringController { .stream() .forEach(cc -> { examProctoringService - .getClientExamCollectingRoomConnectionData( + .getClientExamCollectingRoomConnection( settings, cc.clientConnection) .flatMap(data -> this.sendJoinInstruction( @@ -455,64 +445,16 @@ public class ExamProctoringController { return; } - if (StringUtils.isNotBlank(connectionTokens)) { - // we have defined connection tokens to send instructions to + final boolean definedClients = StringUtils.isNotBlank(connectionTokens); + final boolean inTownhall = this.examProcotringRoomService.getTownhallRoomData(examId).hasValue(); + final boolean roomSpecified = StringUtils.isNotBlank(roomName); - final boolean single = connectionTokens.contains(Constants.LIST_SEPARATOR); - (single - ? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR)) - : Arrays.asList(connectionTokens)) - .stream() - .forEach(connectionToken -> { - this.sebInstructionService.registerInstruction( - examId, - InstructionType.SEB_RECONFIGURE_SETTINGS, - attributes, - connectionToken, - true) - .onError(error -> log.error( - "Failed to register reconfiguring instruction for connection: {}", - connectionToken, - error)); - - }); - } else if (this.examProcotringRoomService.getTownhallRoomData(examId).hasValue()) { - // we are in the town hall so all active connections are involved - - this.examSessionService.getAllActiveConnectionData(examId) - .getOrThrow() - .stream() - .forEach(connection -> { - this.sebInstructionService.registerInstruction( - examId, - InstructionType.SEB_RECONFIGURE_SETTINGS, - attributes, - connection.clientConnection.connectionToken, - true) - .onError(error -> log.error( - "Failed to register reconfiguring instruction for connection: {}", - connection.clientConnection.connectionToken, - error)); - }); - } else if (StringUtils.isNotBlank(roomName)) { - // we have a room name so all connection of this room are involved - - this.examProcotringRoomService.getRoomConnections(examId, roomName) - .getOrThrow() - .stream() - .filter(ExamSessionService.ACTIVE_CONNECTION_FILTER) - .forEach(connection -> { - this.sebInstructionService.registerInstruction( - examId, - InstructionType.SEB_RECONFIGURE_SETTINGS, - attributes, - connection.connectionToken, - true) - .onError(error -> log.error( - "Failed to register reconfiguring instruction for connection: {}", - connection.connectionToken, - error)); - }); + if (definedClients) { + sendBroadcastInstructionsToClients(examId, connectionTokens, attributes); + } else if (inTownhall) { + sendBroadcastInstructionToClientsInExam(examId, attributes); + } else if (roomSpecified) { + sendBroadcastInstructionToClientsInRoom(examId, roomName, attributes); } else { throw new RuntimeException("API attribute validation error: missing " + Domain.REMOTE_PROCTORING_ROOM.ATTR_ID + " and/or" + @@ -520,12 +462,111 @@ public class ExamProctoringController { } } + private void sendBroadcastInstructionsToClients(final Long examId, final String connectionTokens, + final Map attributes) { + final boolean single = connectionTokens.contains(Constants.LIST_SEPARATOR); + (single + ? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR)) + : Arrays.asList(connectionTokens)) + .stream() + .forEach(connectionToken -> { + this.sebInstructionService.registerInstruction( + examId, + InstructionType.SEB_RECONFIGURE_SETTINGS, + attributes, + connectionToken, + true) + .onError(error -> log.error( + "Failed to register reconfiguring instruction for connection: {}", + connectionToken, + error)); + + }); + } + + private void sendBroadcastInstructionToClientsInExam(final Long examId, final Map attributes) { + this.examSessionService + .getAllActiveConnectionData(examId) + .getOrThrow() + .stream() + .forEach(connection -> { + this.sebInstructionService.registerInstruction( + examId, + InstructionType.SEB_RECONFIGURE_SETTINGS, + attributes, + connection.clientConnection.connectionToken, + true) + .onError(error -> log.error( + "Failed to register reconfiguring instruction for connection: {}", + connection.clientConnection.connectionToken, + error)); + }); + } + + private void sendBroadcastInstructionToClientsInRoom( + final Long examId, + final String roomName, + final Map attributes) { + + this.examProcotringRoomService + .getRoomConnections(examId, roomName) + .getOrThrow() + .stream() + .filter(ExamSessionService.ACTIVE_CONNECTION_FILTER) + .forEach(connection -> { + this.sebInstructionService.registerInstruction( + examId, + InstructionType.SEB_RECONFIGURE_SETTINGS, + attributes, + connection.connectionToken, + true) + .onError(error -> log.error( + "Failed to register reconfiguring instruction for connection: {}", + connection.connectionToken, + error)); + }); + } + + private void sendJoinInstructions( + final String connectionTokens, + final ProctoringSettings proctoringSettings) { + + final ExamProctoringService examProctoringService = this.examAdminService + .getExamProctoringService(proctoringSettings.serverType) + .getOrThrow(); + + Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR)) + .stream() + .forEach(connectionToken -> { + sendJoinInstructionToClient(proctoringSettings, examProctoringService, connectionToken); + }); + } + + private void sendJoinInstructionToClient( + final ProctoringSettings proctoringSettings, + final ExamProctoringService examProctoringService, + final String connectionToken) { + + this.examSessionService + .getConnectionData(connectionToken) + .flatMap(connection -> examProctoringService.getClientExamCollectingRoomConnection( + proctoringSettings, + connection.clientConnection)) + .flatMap(data -> this.sendJoinInstruction( + proctoringSettings.examId, + connectionToken, data)) + .onError(error -> log.error("Failed to send rejoin for: {} cause: {}", + connectionToken, + error.getMessage())); + } + private Result sendJoinInstruction( final Long examId, final String connectionToken, - final SEBProctoringConnectionData data) { + final SEBProctoringConnection data) { final Map attributes = new HashMap<>(); + attributes.put( ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.SERVICE_TYPE, ProctoringSettings.ProctoringServerType.JITSI_MEET.name()); @@ -541,6 +582,7 @@ public class ExamProctoringController { attributes.put( ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_TOKEN, data.accessToken); + return this.sebInstructionService.registerInstruction( examId, InstructionType.SEB_PROCTORING, diff --git a/src/main/resources/config/application-gui.properties b/src/main/resources/config/application-gui.properties index 8e729d65..d1e795d6 100644 --- a/src/main/resources/config/application-gui.properties +++ b/src/main/resources/config/application-gui.properties @@ -31,6 +31,8 @@ sebserver.gui.webservice.moodle-lms-enabled=true sebserver.gui.seb.client.config.download.filename=SEBServerSettings.seb sebserver.gui.seb.exam.config.download.filename=SEBExamSettings.seb +sebserver.gui.filter.date.from.years=2 + # remote proctoring sebserver.gui.remote.proctoring.entrypoint=/remote-proctoring sebserver.gui.remote.proctoring.api-servler.endpoint=/remote-view-servlet diff --git a/src/main/resources/config/sql/base/V5_2__insert_new_security_settings_v.1.1.sql b/src/main/resources/config/sql/base/V5_2__insert_new_security_settings_v.1.1.sql new file mode 100644 index 00000000..18207a50 --- /dev/null +++ b/src/main/resources/config/sql/base/V5_2__insert_new_security_settings_v.1.1.sql @@ -0,0 +1,36 @@ +INSERT IGNORE INTO configuration_attribute VALUES + (318, 'sebServiceIgnore', 'CHECKBOX', null, null, null, null, 'true'), + (319, 'allowApplicationLog', 'CHECKBOX', null, null, null, null, 'false'), + (320, 'showApplicationLogButton', 'CHECKBOX', null, null, null, null, 'false'), + (321, 'enableWindowsUpdate', 'CHECKBOX', null, null, null, null, 'false'), + (322, 'enableChromeNotifications', 'CHECKBOX', null, null, null, null, 'false') + ; + + +UPDATE orientation SET y_position='13', width='12' WHERE id='305'; +UPDATE orientation SET y_position='16', width='10' WHERE id='306'; +UPDATE orientation SET y_position='17', width='10' WHERE id='307'; +UPDATE orientation SET y_position='18' WHERE id='317'; +UPDATE orientation SET x_position='0', y_position='9' WHERE id='301'; +UPDATE orientation SET x_position='3', y_position='10', width='4' WHERE id='501'; +UPDATE orientation SET x_position='3', y_position='11', width='4' WHERE id='304'; +UPDATE orientation SET x_position='3', y_position='12', width='4' WHERE id='302'; +UPDATE orientation SET group_id='sebService', x_position='4', y_position='6', width='3' WHERE id='303'; +UPDATE orientation SET group_id='sebService', y_position='2', width='7', title='TOP' WHERE id='300'; +UPDATE orientation SET y_position='3' WHERE id='309'; +UPDATE orientation SET y_position='4' WHERE id='310'; +UPDATE orientation SET y_position='5' WHERE id='311'; +UPDATE orientation SET y_position='6' WHERE id='312'; +UPDATE orientation SET y_position='7' WHERE id='313'; +UPDATE orientation SET y_position='8' WHERE id='314'; +UPDATE orientation SET y_position='11' WHERE id='315'; +UPDATE orientation SET y_position='12' WHERE id='316'; + + +INSERT IGNORE INTO orientation VALUES + (318, 318, 0, 9, 'sebService', 0, 0, 7, 1, 'NONE'), + (319, 319, 0, 9, 'logging', 0, 14, 5, 1, 'NONE'), + (320, 320, 0, 9, 'logging', 0, 15, 5, 1, 'NONE'), + (321, 321, 0, 9, 'sebService', 0, 6, 4, 1, 'NONE'), + (322, 322, 0, 9, 'sebService', 0, 7, 4, 1, 'NONE') + ; \ No newline at end of file diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index bada0fa6..862ff8e0 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -1166,11 +1166,15 @@ sebserver.examconfig.props.label.RTSPUsername=Username sebserver.examconfig.props.label.RTSPPassword=Password -sebserver.examconfig.props.group.servicePolicy=SEB Service policy +sebserver.examconfig.props.group.sebService=SEB Service (Win) +sebserver.examconfig.props.label.sebServicePolicy=IMPORTANT: The SEB Service changes aspects of the system configuration.
Some anti-virus software providers might falsely identify its operation as malicious,
thus it is not recommended to use the SEB Service in uncontrolled environments (e.g. BYOD).


SEB Service policy sebserver.examconfig.props.label.sebServicePolicy.0=allow to run SEB without service sebserver.examconfig.props.label.sebServicePolicy.1=display warning when service is not running sebserver.examconfig.props.label.sebServicePolicy.2=allow to use SEB only with service sebserver.examconfig.props.label.sebServicePolicy.tooltip=Policy that applies when an exam client doesn't have the SEB client running +sebserver.examconfig.props.label.sebServiceIgnore=Ignore SEB Service +sebserver.examconfig.props.label.enableWindowsUpdate=Allow Windows Update to run while SEB is running +sebserver.examconfig.props.label.enableChromeNotifications=Allow notifications from Chrome browsers sebserver.examconfig.props.group.kioskMode=Kiosk Mode sebserver.examconfig.props.label.kioskMode.tooltip=The kiosk mode setting reflects how the computer is locked down in SEB @@ -1232,6 +1236,8 @@ sebserver.examconfig.props.label.logLevel.3=Debug sebserver.examconfig.props.label.logLevel.3.tooltip=Debug is reserved for information which is only necessary for in-deep program code debugging sebserver.examconfig.props.label.logLevel.4=Verbose sebserver.examconfig.props.label.logLevel.4.tooltip=Verbose level contains events of all levels +sebserver.examconfig.props.label.allowApplicationLog=Allow access to application log (Win) +sebserver.examconfig.props.label.showApplicationLogButton=Show log button on taskbar (Win) sebserver.examconfig.props.group.registry=While running SEB sebserver.examconfig.props.group.registry.tooltip=Options in the Windows Security Screen invoked by Ctrl-Alt-Del diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamJITSIProctoringServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamJITSIProctoringServiceTest.java index 311d8b0d..4b138e37 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamJITSIProctoringServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamJITSIProctoringServiceTest.java @@ -11,22 +11,59 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + import org.junit.Test; import org.mockito.Mockito; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ProctoringServerType; -import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData; +import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnection; import ch.ethz.seb.sebserver.gbl.util.Cryptor; public class ExamJITSIProctoringServiceTest { + @Test + public void testTokenPayload() throws InvalidKeyException, NoSuchAlgorithmException { + final Cryptor cryptorMock = Mockito.mock(Cryptor.class); + Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn("fbvgeghergrgrthrehreg123"); + final ExamJITSIProctoringService examJITSIProctoringService = + new ExamJITSIProctoringService(null, null, null, cryptorMock); + + String accessToken = examJITSIProctoringService.createPayload( + "test-app", + "Test Name", + "test-client", + "SomeRoom", + 1609459200L, + "https://test.ch", + false); + + assertEquals( + "{\"context\":{\"user\":{\"name\":\"Test Name\"}},\"iss\":\"test-app\",\"aud\":\"test-client\",\"sub\":\"https://test.ch\",\"room\":\"SomeRoom\",\"moderator\":false,\"exp\":1609459200}", + accessToken); + + accessToken = examJITSIProctoringService.createPayload( + "test-app", + "Test Name", + "test-client", + "SomeRoom", + 1609459200L, + "https://test.ch", + true); + + assertEquals( + "{\"context\":{\"user\":{\"name\":\"Test Name\"}},\"iss\":\"test-app\",\"aud\":\"test-client\",\"sub\":\"https://test.ch\",\"room\":\"SomeRoom\",\"moderator\":true,\"exp\":1609459200}", + accessToken); + } + @Test public void testCreateProctoringURL() { final Cryptor cryptorMock = Mockito.mock(Cryptor.class); Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn("fbvgeghergrgrthrehreg123"); final ExamJITSIProctoringService examJITSIProctoringService = new ExamJITSIProctoringService(null, null, null, cryptorMock); - final SEBProctoringConnectionData data = examJITSIProctoringService.createProctoringConnectionData( + final SEBProctoringConnection data = examJITSIProctoringService.createProctoringConnection( ProctoringServerType.JITSI_MEET, "connectionToken", "https://seb-jitsi.example.ch", @@ -36,15 +73,17 @@ public class ExamJITSIProctoringServiceTest { "test-client", "SomeRoom", "Subject", - 1609459200L) + 1609459200L, + true) .getOrThrow(); assertNotNull(data); assertEquals( "https://seb-jitsi.example.ch", data.serverURL); + assertEquals( - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0Ijp7InVzZXIiOnsibmFtZSI6IlRlc3QgTmFtZSJ9fSwiaXNzIjoidGVzdC1hcHAiLCJhdWQiOiJ0ZXN0LWNsaWVudCIsInN1YiI6InNlYi1qaXRzaS5leGFtcGxlLmNoIiwicm9vbSI6IlNvbWVSb29tIiwiZXhwIjoxNjA5NDU5MjAwfQ.4ovqUkG6jrLvkDEZNdhbtFI_DFLDFsM2eBJHhcYq7a4", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0Ijp7InVzZXIiOnsibmFtZSI6IlRlc3QgTmFtZSJ9fSwiaXNzIjoidGVzdC1hcHAiLCJhdWQiOiJ0ZXN0LWNsaWVudCIsInN1YiI6InNlYi1qaXRzaS5leGFtcGxlLmNoIiwicm9vbSI6IlNvbWVSb29tIiwibW9kZXJhdG9yIjp0cnVlLCJleHAiOjE2MDk0NTkyMDB9.poOwfCsRjNqCizEQM3qFFWbjuX0bZLer3cqlbaPK9wc", data.accessToken); }