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:
		
						commit
						36f546960b
					
				
					 11 changed files with 129 additions and 68 deletions
				
			
		| 
						 | 
					@ -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;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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());
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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()) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue