Merge remote-tracking branch 'origin/dev-1.2' into development

Conflicts:
	src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java
	src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java
This commit is contained in:
anhefti 2021-09-08 16:10:36 +02:00
commit 36f546960b
11 changed files with 129 additions and 68 deletions

View file

@ -49,6 +49,7 @@ public class ProctoringServiceSettings implements Entity {
public static final String ATTR_ENABLED_FEATURES = "enabledFeatures"; public static final String ATTR_ENABLED_FEATURES = "enabledFeatures";
public static final String ATTR_COLLECT_ALL_ROOM_NAME = "collectAllRoomName"; public static final String ATTR_COLLECT_ALL_ROOM_NAME = "collectAllRoomName";
public static final String ATTR_SERVICE_IN_USE = "serviceInUse"; public static final String ATTR_SERVICE_IN_USE = "serviceInUse";
public static final String ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM = "useZoomAppClientForCollectingRoom";
@JsonProperty(Domain.EXAM.ATTR_ID) @JsonProperty(Domain.EXAM.ATTR_ID)
public final Long examId; public final Long examId;
@ -84,6 +85,9 @@ public class ProctoringServiceSettings implements Entity {
@JsonProperty(ATTR_SERVICE_IN_USE) @JsonProperty(ATTR_SERVICE_IN_USE)
public final Boolean serviceInUse; public final Boolean serviceInUse;
@JsonProperty(ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM)
public final Boolean useZoomAppClientForCollectingRoom;
@JsonCreator @JsonCreator
public ProctoringServiceSettings( public ProctoringServiceSettings(
@JsonProperty(Domain.EXAM.ATTR_ID) final Long examId, @JsonProperty(Domain.EXAM.ATTR_ID) final Long examId,
@ -96,7 +100,8 @@ public class ProctoringServiceSettings implements Entity {
@JsonProperty(ATTR_APP_KEY) final String appKey, @JsonProperty(ATTR_APP_KEY) final String appKey,
@JsonProperty(ATTR_APP_SECRET) final CharSequence appSecret, @JsonProperty(ATTR_APP_SECRET) final CharSequence appSecret,
@JsonProperty(ATTR_SDK_KEY) final String sdkKey, @JsonProperty(ATTR_SDK_KEY) final String sdkKey,
@JsonProperty(ATTR_SDK_SECRET) final CharSequence sdkSecret) { @JsonProperty(ATTR_SDK_SECRET) final CharSequence sdkSecret,
@JsonProperty(ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM) final Boolean useZoomAppClientForCollectingRoom) {
this.examId = examId; this.examId = examId;
this.enableProctoring = BooleanUtils.isTrue(enableProctoring); this.enableProctoring = BooleanUtils.isTrue(enableProctoring);
@ -109,7 +114,7 @@ public class ProctoringServiceSettings implements Entity {
this.appSecret = appSecret; this.appSecret = appSecret;
this.sdkKey = sdkKey; this.sdkKey = sdkKey;
this.sdkSecret = sdkSecret; this.sdkSecret = sdkSecret;
this.useZoomAppClientForCollectingRoom = BooleanUtils.toBoolean(useZoomAppClientForCollectingRoom);
} }
@Override @Override
@ -171,6 +176,10 @@ public class ProctoringServiceSettings implements Entity {
return this.serviceInUse; return this.serviceInUse;
} }
public Boolean getUseZoomAppClientForCollectingRoom() {
return this.useZoomAppClientForCollectingRoom;
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;

View file

@ -389,7 +389,7 @@ public class ExamForm implements TemplateComposer {
.withExec(this.examCreateClientConfigPopup.exportFunction( .withExec(this.examCreateClientConfigPopup.exportFunction(
exam.institutionId, exam.institutionId,
exam.getName())) exam.getName()))
.publishIf(() -> writeGrant && readonly) .publishIf(() -> editable && readonly)
.newAction(ActionDefinition.EXAM_MODIFY_SEB_RESTRICTION_DETAILS) .newAction(ActionDefinition.EXAM_MODIFY_SEB_RESTRICTION_DETAILS)
.withEntityKey(entityKey) .withEntityKey(entityKey)
@ -415,13 +415,13 @@ public class ExamForm implements TemplateComposer {
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant)) .withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant))
.noEventPropagation() .noEventPropagation()
.publishIf(() -> proctoringEnabled && readonly) .publishIf(() -> editable && proctoringEnabled && readonly)
.newAction(ActionDefinition.EXAM_PROCTORING_OFF) .newAction(ActionDefinition.EXAM_PROCTORING_OFF)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant)) .withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant))
.noEventPropagation() .noEventPropagation()
.publishIf(() -> !proctoringEnabled && readonly) .publishIf(() -> editable && !proctoringEnabled && readonly)
.newAction(ActionDefinition.EXAM_DELETE) .newAction(ActionDefinition.EXAM_DELETE)
.withEntityKey(entityKey) .withEntityKey(entityKey)

View file

@ -76,6 +76,8 @@ public class ExamProctoringSettings {
new LocTextKey("sebserver.exam.proctoring.form.sdkkey"); new LocTextKey("sebserver.exam.proctoring.form.sdkkey");
private final static LocTextKey SEB_PROCTORING_FORM_SDKSECRET = private final static LocTextKey SEB_PROCTORING_FORM_SDKSECRET =
new LocTextKey("sebserver.exam.proctoring.form.sdksecret"); new LocTextKey("sebserver.exam.proctoring.form.sdksecret");
private final static LocTextKey SEB_PROCTORING_FORM_USE_ZOOM_APP_CLIENT =
new LocTextKey("sebserver.exam.proctoring.form.useZoomAppClient");
private final static LocTextKey SEB_PROCTORING_FORM_FEATURES = private final static LocTextKey SEB_PROCTORING_FORM_FEATURES =
new LocTextKey("sebserver.exam.proctoring.form.features"); new LocTextKey("sebserver.exam.proctoring.form.features");
@ -162,7 +164,9 @@ public class ExamProctoringSettings {
form.getFieldValue(ProctoringServiceSettings.ATTR_APP_KEY), form.getFieldValue(ProctoringServiceSettings.ATTR_APP_KEY),
form.getFieldValue(ProctoringServiceSettings.ATTR_APP_SECRET), form.getFieldValue(ProctoringServiceSettings.ATTR_APP_SECRET),
form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_KEY), form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_KEY),
form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_SECRET)); form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_SECRET),
BooleanUtils.toBoolean(form.getFieldValue(
ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM)));
} catch (final Exception e) { } catch (final Exception e) {
log.error("Unexpected error while trying to get settings from form: ", e); log.error("Unexpected error while trying to get settings from form: ", e);
@ -232,8 +236,6 @@ public class ExamProctoringSettings {
.copyOf(content) .copyOf(content)
.clearEntityKeys(); .clearEntityKeys();
final boolean isZoom = proctoringSettings.serverType == ProctoringServerType.ZOOM;
final FormHandle<ProctoringServiceSettings> formHandle = this.pageService.formBuilder( final FormHandle<ProctoringServiceSettings> formHandle = this.pageService.formBuilder(
formContext) formContext)
.withDefaultSpanInput(5) .withDefaultSpanInput(5)
@ -258,8 +260,7 @@ public class ExamProctoringSettings {
ProctoringServiceSettings.ATTR_SERVER_TYPE, ProctoringServiceSettings.ATTR_SERVER_TYPE,
SEB_PROCTORING_FORM_TYPE, SEB_PROCTORING_FORM_TYPE,
proctoringSettings.serverType.name(), proctoringSettings.serverType.name(),
resourceService::examProctoringTypeResources) resourceService::examProctoringTypeResources))
.withSelectionListener(this::serviceSelection))
.addField(FormBuilder.text( .addField(FormBuilder.text(
ProctoringServiceSettings.ATTR_SERVER_URL, ProctoringServiceSettings.ATTR_SERVER_URL,
@ -282,8 +283,7 @@ public class ExamProctoringSettings {
.addField(FormBuilder.text( .addField(FormBuilder.text(
ProctoringServiceSettings.ATTR_SDK_KEY, ProctoringServiceSettings.ATTR_SDK_KEY,
SEB_PROCTORING_FORM_SDKKEY, SEB_PROCTORING_FORM_SDKKEY,
proctoringSettings.sdkKey) proctoringSettings.sdkKey))
.visibleIf(isZoom))
.withEmptyCellSeparation(false) .withEmptyCellSeparation(false)
.addField(FormBuilder.password( .addField(FormBuilder.password(
@ -291,8 +291,7 @@ public class ExamProctoringSettings {
SEB_PROCTORING_FORM_SDKSECRET, SEB_PROCTORING_FORM_SDKSECRET,
(proctoringSettings.sdkSecret != null) (proctoringSettings.sdkSecret != null)
? String.valueOf(proctoringSettings.sdkSecret) ? String.valueOf(proctoringSettings.sdkSecret)
: null) : null))
.visibleIf(isZoom))
.withDefaultSpanInput(1) .withDefaultSpanInput(1)
.addField(FormBuilder.text( .addField(FormBuilder.text(
@ -304,6 +303,14 @@ public class ExamProctoringSettings {
.withDefaultSpanEmptyCell(4) .withDefaultSpanEmptyCell(4)
.withDefaultSpanInput(5) .withDefaultSpanInput(5)
.addField(FormBuilder.checkbox(
ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM,
SEB_PROCTORING_FORM_USE_ZOOM_APP_CLIENT,
String.valueOf(proctoringSettings.useZoomAppClientForCollectingRoom)))
.withDefaultSpanInput(5)
.withEmptyCellSeparation(true)
.withDefaultSpanEmptyCell(1)
.addField(FormBuilder.multiCheckboxSelection( .addField(FormBuilder.multiCheckboxSelection(
ProctoringServiceSettings.ATTR_ENABLED_FEATURES, ProctoringServiceSettings.ATTR_ENABLED_FEATURES,
SEB_PROCTORING_FORM_FEATURES, SEB_PROCTORING_FORM_FEATURES,
@ -319,14 +326,6 @@ public class ExamProctoringSettings {
return () -> formHandle; return () -> formHandle;
} }
private void serviceSelection(final Form form) {
final boolean isZoom = ProctoringServerType.ZOOM.name()
.equals(form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_TYPE));
form.setFieldVisible(isZoom, ProctoringServiceSettings.ATTR_SDK_KEY);
form.setFieldVisible(isZoom, ProctoringServiceSettings.ATTR_SDK_SECRET);
}
} }
} }

View file

@ -13,6 +13,7 @@ import java.util.Collections;
import java.util.Map; import java.util.Map;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor; import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Color;
@ -83,14 +84,19 @@ public class MonitoringProctoringService {
static final String OPEN_ROOM_SCRIPT = static final String OPEN_ROOM_SCRIPT =
"try {\n" + "try {\n" +
"var existingWin = window.open('', '%s', 'height=%s,width=%s,location=no,scrollbars=yes,status=no,menubar=0,toolbar=no,titlebar=no,dialog=no');\n" + "var existingWin = window.open('', '%s', 'height=%s,width=%s,location=no,scrollbars=yes,status=no,menubar=0,toolbar=no,titlebar=no,dialog=no');\n" +
"existingWin.document.title = '%s';\n" + "try {\n" +
"if(existingWin.location.href === 'about:blank'){\n" + "if(existingWin.location.href === 'about:blank'){\n" +
" existingWin.document.title = '%s';\n" +
" existingWin.location.href = '%s%s';\n" + " existingWin.location.href = '%s%s';\n" +
" existingWin.focus();\n" + " existingWin.focus();\n" +
"} else {\n" + "} else {\n" +
" existingWin.focus();\n" + " existingWin.focus();\n" +
"}" + "}" +
"}\n" + "} catch(secErr) {\n" +
" alert(\"Unexpected Javascript Error happened: \" + secErr);\n"+
" existingWin.focus();\n" +
"}" +
"}" +
"catch(err) {\n" + "catch(err) {\n" +
" alert(\"Unexpected Javascript Error happened: \" + err);\n"+ " alert(\"Unexpected Javascript Error happened: \" + err);\n"+
"}"; "}";
@ -288,35 +294,68 @@ public class MonitoringProctoringService {
String.valueOf(proctoringSettings.examId), String.valueOf(proctoringSettings.examId),
proctoringConnectionData); proctoringConnectionData);
final String script = String.format( if (proctoringSettings.useZoomAppClientForCollectingRoom &&
OPEN_ROOM_SCRIPT, StringUtils.isNotBlank(extractZoomStartLink(room))) {
room.name,
800,
1200,
room.name,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
RWT.getClient() final String startLink = extractZoomStartLink(room);
.getService(JavaScriptExecutor.class) final String script = String.format(
.execute(script); OPEN_ROOM_SCRIPT,
room.name,
800,
1200,
room.name,
startLink,
"");
final boolean newWindow = this.pageService.getCurrentUser() RWT.getClient()
.getProctoringGUIService() .getService(JavaScriptExecutor.class)
.registerProctoringWindow(String.valueOf(room.examId), room.name, room.name); .execute(script);
} else {
final String script = String.format(
OPEN_ROOM_SCRIPT,
room.name,
800,
1200,
room.name,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
RWT.getClient()
.getService(JavaScriptExecutor.class)
.execute(script);
final boolean newWindow = this.pageService.getCurrentUser()
.getProctoringGUIService()
.registerProctoringWindow(String.valueOf(room.examId), room.name, room.name);
if (newWindow) {
this.pageService.getRestService()
.getBuilder(NotifyProctoringRoomOpened.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
.call()
.onError(error -> log.error("Failed to notify proctoring room opened: ", error));
}
if (newWindow) {
this.pageService.getRestService()
.getBuilder(NotifyProctoringRoomOpened.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
.call()
.onError(error -> log.error("Failed to notify proctoring room opened: ", error));
} }
return action; return action;
} }
private String extractZoomStartLink(final RemoteProctoringRoom room) {
try {
final Map<String, String> data =
this.jsonMapper.readValue(room.additionalRoomData, new TypeReference<Map<String, String>>() {
});
return data.get("start_url");
} catch (final Exception e) {
log.error("Failed to extract Zoom start link: ", e);
return null;
}
}
public PageAction openOneToOneRoom( public PageAction openOneToOneRoom(
final PageAction action, final PageAction action,
final ClientConnectionData connectionData, final ClientConnectionData connectionData,
@ -349,9 +388,9 @@ public class MonitoringProctoringService {
final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class); final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
final String script = String.format( final String script = String.format(
MonitoringProctoringService.OPEN_ROOM_SCRIPT, MonitoringProctoringService.OPEN_ROOM_SCRIPT,
connectionToken, connectionData.clientConnection.userSessionId,
420, 800,
640, 1200,
connectionData.clientConnection.userSessionId, connectionData.clientConnection.userSessionId,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(), this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint); this.remoteProctoringEndpoint);

View file

@ -849,7 +849,7 @@ public class ExamDAOImpl implements ExamDAO {
// No course access on the LMS. This means we can't get any quizzes from this LMSSetup at the moment // No course access on the LMS. This means we can't get any quizzes from this LMSSetup at the moment
// All exams are marked as corrupt because of LMS Setup failure // All exams are marked as corrupt because of LMS Setup failure
log.warn("Failed to get quizzes form LMS Setup. No access to LMS {}", lmsSetup); log.warn("Failed to get quizzes form LMS Setup. No access to LMS {}", lmsSetup.lmsSetup());
return recordMapping.entrySet() return recordMapping.entrySet()
.stream() .stream()

View file

@ -215,7 +215,8 @@ public class ExamAdminServiceImpl implements ExamAdminService {
getString(mapping, ProctoringServiceSettings.ATTR_APP_KEY), getString(mapping, ProctoringServiceSettings.ATTR_APP_KEY),
getString(mapping, ProctoringServiceSettings.ATTR_APP_SECRET), getString(mapping, ProctoringServiceSettings.ATTR_APP_SECRET),
getString(mapping, ProctoringServiceSettings.ATTR_SDK_KEY), getString(mapping, ProctoringServiceSettings.ATTR_SDK_KEY),
getString(mapping, ProctoringServiceSettings.ATTR_SDK_SECRET)); getString(mapping, ProctoringServiceSettings.ATTR_SDK_SECRET),
getBoolean(mapping, ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM));
}); });
} }
@ -287,6 +288,12 @@ public class ExamAdminServiceImpl implements ExamAdminService {
ProctoringServiceSettings.ATTR_ENABLED_FEATURES, ProctoringServiceSettings.ATTR_ENABLED_FEATURES,
StringUtils.join(proctoringServiceSettings.enabledFeatures, Constants.LIST_SEPARATOR)); StringUtils.join(proctoringServiceSettings.enabledFeatures, Constants.LIST_SEPARATOR));
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
examId,
ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM,
String.valueOf(proctoringServiceSettings.useZoomAppClientForCollectingRoom));
return proctoringServiceSettings; return proctoringServiceSettings;
}); });
} }
@ -334,6 +341,14 @@ public class ExamAdminServiceImpl implements ExamAdminService {
} }
} }
private Boolean getBoolean(final Map<String, AdditionalAttributeRecord> mapping, final String name) {
if (mapping.containsKey(name)) {
return BooleanUtils.toBooleanObject(mapping.get(name).getValue());
} else {
return false;
}
}
private Integer getCollectingRoomSize(final Map<String, AdditionalAttributeRecord> mapping) { private Integer getCollectingRoomSize(final Map<String, AdditionalAttributeRecord> mapping) {
if (mapping.containsKey(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE)) { if (mapping.containsKey(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE)) {
return Integer.valueOf(mapping.get(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE).getValue()); return Integer.valueOf(mapping.get(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE).getValue());

View file

@ -133,7 +133,7 @@ class ExamSessionControlTask implements DisposableBean {
.getOrThrow() .getOrThrow()
.stream() .stream()
.filter(exam -> exam.startTime.minus(this.examTimePrefix).isBefore(now)) .filter(exam -> exam.startTime.minus(this.examTimePrefix).isBefore(now))
.map(exam -> this.examUpdateHandler.setRunning(exam, updateId)) .flatMap(exam -> Result.skipOnError(this.examUpdateHandler.setRunning(exam, updateId)))
.collect(Collectors.toMap(Exam::getId, Exam::getName)); .collect(Collectors.toMap(Exam::getId, Exam::getName));
if (!updated.isEmpty()) { if (!updated.isEmpty()) {
@ -158,10 +158,8 @@ class ExamSessionControlTask implements DisposableBean {
.getOrThrow() .getOrThrow()
.stream() .stream()
.filter(exam -> exam.endTime != null && exam.endTime.plus(this.examTimeSuffix).isBefore(now)) .filter(exam -> exam.endTime != null && exam.endTime.plus(this.examTimeSuffix).isBefore(now))
.map(exam -> this.examUpdateHandler.setFinished(exam, updateId)) .flatMap(exam -> Result.skipOnError(this.examUpdateHandler.setFinished(exam, updateId)))
.map(this.examProcotringRoomService::disposeRoomsForExam) .flatMap(exam -> Result.skipOnError(this.examProcotringRoomService.disposeRoomsForExam(exam)))
.filter(result -> !result.hasError())
.map(Result::get)
.collect(Collectors.toMap(Exam::getId, Exam::getName)); .collect(Collectors.toMap(Exam::getId, Exam::getName));
if (!updated.isEmpty()) { if (!updated.isEmpty()) {

View file

@ -64,14 +64,15 @@ class ExamUpdateHandler {
final DateTime now = DateTime.now(DateTimeZone.UTC); final DateTime now = DateTime.now(DateTimeZone.UTC);
if (exam.getStatus() == ExamStatus.UP_COMING if (exam.getStatus() == ExamStatus.UP_COMING
&& exam.endTime.plus(this.examTimeSuffix).isBefore(now)) { && exam.endTime.plus(this.examTimeSuffix).isBefore(now)) {
return setRunning(exam, this.createUpdateId()); return setRunning(exam, this.createUpdateId())
.getOr(exam);
} else { } else {
return exam; return exam;
} }
}); });
} }
Exam setRunning(final Exam exam, final String updateId) { Result<Exam> setRunning(final Exam exam, final String updateId) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Update exam as running: {}", exam); log.debug("Update exam as running: {}", exam);
} }
@ -85,11 +86,10 @@ class ExamUpdateHandler {
.flatMap(this.sebRestrictionService::applySEBClientRestriction) .flatMap(this.sebRestrictionService::applySEBClientRestriction)
.flatMap(e -> this.examDAO.releaseLock(e.id, updateId)) .flatMap(e -> this.examDAO.releaseLock(e.id, updateId))
.onError(error -> this.examDAO.forceUnlock(exam.id) .onError(error -> this.examDAO.forceUnlock(exam.id)
.onError(unlockError -> log.error("Failed to force unlock update look for exam: {}", exam.id))) .onError(unlockError -> log.error("Failed to force unlock update look for exam: {}", exam.id)));
.getOrThrow();
} }
Exam setFinished(final Exam exam, final String updateId) { Result<Exam> setFinished(final Exam exam, final String updateId) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Update exam as finished: {}", exam); log.debug("Update exam as finished: {}", exam);
} }
@ -102,8 +102,7 @@ class ExamUpdateHandler {
updateId)) updateId))
.flatMap(this.sebRestrictionService::releaseSEBClientRestriction) .flatMap(this.sebRestrictionService::releaseSEBClientRestriction)
.flatMap(e -> this.examDAO.releaseLock(e.id, updateId)) .flatMap(e -> this.examDAO.releaseLock(e.id, updateId))
.onError(error -> this.examDAO.forceUnlock(exam.id)) .onError(error -> this.examDAO.forceUnlock(exam.id));
.getOrThrow();
} }
} }

View file

@ -89,7 +89,7 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
@PathVariable final Long modelId) { @PathVariable final Long modelId) {
this.authorization.check( this.authorization.check(
PrivilegeType.MODIFY, PrivilegeType.READ,
EntityType.LMS_SETUP, EntityType.LMS_SETUP,
institutionId); institutionId);

View file

@ -665,10 +665,10 @@ sebserver.exam.proctoring.form.appkey=App Key
sebserver.exam.proctoring.form.appkey.tooltip=The application key of the proctoring service server sebserver.exam.proctoring.form.appkey.tooltip=The application key of the proctoring service server
sebserver.exam.proctoring.form.secret=App Secret sebserver.exam.proctoring.form.secret=App Secret
sebserver.exam.proctoring.form.secret.tooltip=The secret used to access the proctoring service sebserver.exam.proctoring.form.secret.tooltip=The secret used to access the proctoring service
sebserver.exam.proctoring.form.sdkkey=SDK Key (MacOS/iOS) sebserver.exam.proctoring.form.sdkkey=SDK Key (Zoom - MacOS/iOS)
sebserver.exam.proctoring.form.sdkkey.tooltip=The SDK key and secret are used for live proctoring with SEB clients for iOS and/or MacOS sebserver.exam.proctoring.form.sdkkey.tooltip=The SDK key and secret are used for live proctoring with SEB clients for iOS and/or MacOS<br/>This is only relevant for proctoring with Zoom service.
sebserver.exam.proctoring.form.sdksecret=SDK Secret (MacOS/iOS) sebserver.exam.proctoring.form.sdksecret=SDK Secret (Zoom - MacOS/iOS)
sebserver.exam.proctoring.form.sdksecret.tooltip=The SDK key and secret are used for live proctoring with SEB clients for iOS and/or MacOS sebserver.exam.proctoring.form.sdksecret.tooltip=The SDK key and secret are used for live proctoring with SEB clients for iOS and/or MacOS<br/>This is only relevant for proctoring with Zoom service.
sebserver.exam.proctoring.form.features=Enabled Features sebserver.exam.proctoring.form.features=Enabled Features
sebserver.exam.proctoring.form.features.TOWN_HALL=Town-Hall Room sebserver.exam.proctoring.form.features.TOWN_HALL=Town-Hall Room
sebserver.exam.proctoring.form.features.ONE_TO_ONE=One to One Room sebserver.exam.proctoring.form.features.ONE_TO_ONE=One to One Room
@ -677,6 +677,8 @@ sebserver.exam.proctoring.form.features.ENABLE_CHAT=Chat Feature
sebserver.exam.proctoring.form.features.WAITING_ROOM=Enable waiting room for collecting rooms sebserver.exam.proctoring.form.features.WAITING_ROOM=Enable waiting room for collecting rooms
sebserver.exam.proctoring.form.features.SEND_REJOIN_COLLECTING_ROOM=Force rejoin for collecting rooms sebserver.exam.proctoring.form.features.SEND_REJOIN_COLLECTING_ROOM=Force rejoin for collecting rooms
sebserver.exam.proctoring.form.features.RESET_BROADCAST_ON_LAVE=Reset broadcast on leave sebserver.exam.proctoring.form.features.RESET_BROADCAST_ON_LAVE=Reset broadcast on leave
sebserver.exam.proctoring.form.useZoomAppClient=Use Zoom App-Client
sebserver.exam.proctoring.form.useZoomAppClient.tooltip=If this is set SEB Server opens a start link for the meeting instead of a new popup-window with the Zoom Web Client.<br/>A Zoom App Client must already be installed on the proctor's device or can be installed by following the instructions shown in the browser window.
sebserver.exam.proctoring.type.servertype.JITSI_MEET=Jitsi Meet Server sebserver.exam.proctoring.type.servertype.JITSI_MEET=Jitsi Meet Server
sebserver.exam.proctoring.type.servertype.JITSI_MEET.tooltip=Use a Jitsi Meet server for proctoring sebserver.exam.proctoring.type.servertype.JITSI_MEET.tooltip=Use a Jitsi Meet server for proctoring

View file

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