Merge remote-tracking branch 'origin/dev-1.1.0' into development
Conflicts: src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java
This commit is contained in:
commit
ad7f06e521
24 changed files with 590 additions and 373 deletions
2
pom.xml
2
pom.xml
|
@ -18,7 +18,7 @@
|
|||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<sebserver-version>1.1.0-rc1</sebserver-version>
|
||||
<sebserver-version>1.2.0-SNAPSHOT</sebserver-version>
|
||||
<build-version>${sebserver-version}</build-version>
|
||||
<revision>${sebserver-version}</revision>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
|
|
@ -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,
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<>(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<Configuration>.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<Configuration>.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> configuration = restCall
|
||||
.call();
|
||||
|
||||
final Result<Configuration> 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<ConfigurationNode> 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<FormHandle<ConfigurationNode>> {
|
||||
|
||||
private final PageService pageService;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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<SEBProctoringConnectionData> {
|
||||
public class ActivateTownhallRoom extends RestCall<SEBProctoringConnection> {
|
||||
|
||||
public ActivateTownhallRoom() {
|
||||
super(new TypeKey<>(
|
||||
CallType.UNDEFINED,
|
||||
EntityType.EXAM_PROCTOR_DATA,
|
||||
new TypeReference<SEBProctoringConnectionData>() {
|
||||
new TypeReference<SEBProctoringConnection>() {
|
||||
}),
|
||||
HttpMethod.POST,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
|
|
|
@ -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<SEBProctoringConnectionData> {
|
||||
public class GetProctorRoomConnectionData extends RestCall<SEBProctoringConnection> {
|
||||
|
||||
public GetProctorRoomConnectionData() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_SINGLE,
|
||||
EntityType.EXAM_PROCTOR_DATA,
|
||||
new TypeReference<SEBProctoringConnectionData>() {
|
||||
new TypeReference<SEBProctoringConnection>() {
|
||||
}),
|
||||
HttpMethod.GET,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
|
|
|
@ -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<SEBProctoringConnectionData> {
|
||||
public class SendJoinRemoteProctoringRoom extends RestCall<SEBProctoringConnection> {
|
||||
|
||||
public SendJoinRemoteProctoringRoom() {
|
||||
super(new TypeKey<>(
|
||||
CallType.UNDEFINED,
|
||||
EntityType.EXAM_PROCTOR_DATA,
|
||||
new TypeReference<SEBProctoringConnectionData>() {
|
||||
new TypeReference<SEBProctoringConnection>() {
|
||||
}),
|
||||
HttpMethod.POST,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
|
|
|
@ -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<SEBProctoringConnectionData> registerNewSingleProcotringRoom(
|
||||
public Result<SEBProctoringConnection> 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<SEBProctoringConnectionData> registerTownhallRoom(
|
||||
public Result<SEBProctoringConnection> 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<SEBProctoringConnectionData> registerNewProcotringRoom(
|
||||
public Result<SEBProctoringConnection> 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;
|
||||
}
|
||||
|
|
|
@ -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<List<QuizData>> 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
|
||||
|
|
|
@ -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<List<QuizData>> 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
|
||||
|
|
|
@ -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<Boolean> testExamProctoring(final ProctoringSettings examProctoring);
|
||||
|
||||
Result<SEBProctoringConnectionData> 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<SEBProctoringConnection> createProctorPublicRoomConnection(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final String roomName,
|
||||
final String subject);
|
||||
|
||||
Result<SEBProctoringConnectionData> getClientExamCollectingRoomConnectionData(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final String connectionToken);
|
||||
|
||||
Result<SEBProctoringConnectionData> getClientExamCollectingRoomConnectionData(
|
||||
Result<SEBProctoringConnection> getClientExamCollectingRoomConnection(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final ClientConnection connection);
|
||||
|
||||
Result<SEBProctoringConnectionData> getClientExamCollectingRoomConnectionData(
|
||||
Result<SEBProctoringConnection> getClientExamCollectingRoomConnection(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final String connectionToken,
|
||||
final String roomName,
|
||||
final String subject);
|
||||
|
||||
Result<SEBProctoringConnectionData> getClientRoomConnectionData(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final String connectionToken);
|
||||
|
||||
Result<SEBProctoringConnectionData> getClientRoomConnectionData(
|
||||
Result<SEBProctoringConnection> getClientRoomConnection(
|
||||
final ProctoringSettings examProctoring,
|
||||
final String connectionToken,
|
||||
final String roomName,
|
||||
final String subject);
|
||||
|
||||
Result<SEBProctoringConnectionData> createProctoringConnectionData(
|
||||
Result<SEBProctoringConnection> 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<String> createClientAccessToken(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
|
|
|
@ -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<SEBProctoringConnectionData> createProctorPublicRoomConnection(
|
||||
public Result<SEBProctoringConnection> 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<SEBProctoringConnectionData> getClientExamCollectingRoomConnectionData(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final String connectionToken) {
|
||||
|
||||
return this.examSessionService
|
||||
.getConnectionData(connectionToken)
|
||||
.flatMap(connection -> getClientExamCollectingRoomConnectionData(
|
||||
proctoringSettings,
|
||||
connection.clientConnection));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<SEBProctoringConnectionData> getClientExamCollectingRoomConnectionData(
|
||||
public Result<SEBProctoringConnection> 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<SEBProctoringConnectionData> getClientExamCollectingRoomConnectionData(
|
||||
public Result<SEBProctoringConnection> 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<SEBProctoringConnectionData> 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<SEBProctoringConnectionData> getClientRoomConnectionData(
|
||||
public Result<SEBProctoringConnection> 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<SEBProctoringConnectionData> createProctoringConnectionData(
|
||||
public Result<SEBProctoringConnection> 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");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<String, String> 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<String, String> 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<String, String> 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<Void> sendJoinInstruction(
|
||||
final Long examId,
|
||||
final String connectionToken,
|
||||
final SEBProctoringConnectionData data) {
|
||||
final SEBProctoringConnection data) {
|
||||
|
||||
final Map<String, String> 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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
;
|
|
@ -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=<span style=\"color:#aa0000\">IMPORTANT: The SEB Service changes aspects of the system configuration.<br/>Some anti-virus software providers might falsely identify its operation as malicious,<br/>thus it is not recommended to use the SEB Service in uncontrolled environments (e.g. BYOD).</span><br/><br/>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
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue