SEBSERV-363 finished integration adaption and testing
This commit is contained in:
		
							parent
							
								
									edce7275ca
								
							
						
					
					
						commit
						4671e682a3
					
				
					 13 changed files with 209 additions and 124 deletions
				
			
		| 
						 | 
					@ -126,7 +126,7 @@ public class ProctoringServiceSettings implements Entity {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.examId = examId;
 | 
					        this.examId = examId;
 | 
				
			||||||
        this.enableProctoring = BooleanUtils.isTrue(enableProctoring);
 | 
					        this.enableProctoring = BooleanUtils.isTrue(enableProctoring);
 | 
				
			||||||
        this.serverType = (serverType != null) ? serverType : ProctoringServerType.JITSI_MEET;
 | 
					        this.serverType = (serverType != null) ? serverType : ProctoringServerType.ZOOM;
 | 
				
			||||||
        this.serverURL = serverURL;
 | 
					        this.serverURL = serverURL;
 | 
				
			||||||
        this.collectingRoomSize = (collectingRoomSize != null) ? collectingRoomSize : 20;
 | 
					        this.collectingRoomSize = (collectingRoomSize != null) ? collectingRoomSize : 20;
 | 
				
			||||||
        this.enabledFeatures = enabledFeatures != null ? enabledFeatures : EnumSet.allOf(ProctoringFeature.class);
 | 
					        this.enabledFeatures = enabledFeatures != null ? enabledFeatures : EnumSet.allOf(ProctoringFeature.class);
 | 
				
			||||||
| 
						 | 
					@ -144,7 +144,7 @@ public class ProctoringServiceSettings implements Entity {
 | 
				
			||||||
    public ProctoringServiceSettings(final Long examId) {
 | 
					    public ProctoringServiceSettings(final Long examId) {
 | 
				
			||||||
        this.examId = examId;
 | 
					        this.examId = examId;
 | 
				
			||||||
        this.enableProctoring = false;
 | 
					        this.enableProctoring = false;
 | 
				
			||||||
        this.serverType = null;
 | 
					        this.serverType = ProctoringServerType.ZOOM;
 | 
				
			||||||
        this.serverURL = null;
 | 
					        this.serverURL = null;
 | 
				
			||||||
        this.collectingRoomSize = 20;
 | 
					        this.collectingRoomSize = 20;
 | 
				
			||||||
        this.enabledFeatures = EnumSet.allOf(ProctoringFeature.class);
 | 
					        this.enabledFeatures = EnumSet.allOf(ProctoringFeature.class);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -256,7 +256,6 @@ public class ExamSignatureKeyForm implements TemplateComposer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                .newAction(ActionDefinition.EXAM_SECURITY_KEY_BACK_MODIFY)
 | 
					                .newAction(ActionDefinition.EXAM_SECURITY_KEY_BACK_MODIFY)
 | 
				
			||||||
                .withEntityKey(entityKey)
 | 
					                .withEntityKey(entityKey)
 | 
				
			||||||
                //.withExec(this.pageService.backToCurrentFunction())
 | 
					 | 
				
			||||||
                .publishIf(() -> readonly)
 | 
					                .publishIf(() -> readonly)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                .newAction(ActionDefinition.EXAM_SECURITY_KEY_SHOW_ADD_GRANT_POPUP)
 | 
					                .newAction(ActionDefinition.EXAM_SECURITY_KEY_SHOW_ADD_GRANT_POPUP)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -111,6 +111,8 @@ public class ProctoringSettingsPopup {
 | 
				
			||||||
            new LocTextKey("sebserver.exam.proctoring.form.resetSettings");
 | 
					            new LocTextKey("sebserver.exam.proctoring.form.resetSettings");
 | 
				
			||||||
    private final static LocTextKey RESET_CONFIRM_KEY =
 | 
					    private final static LocTextKey RESET_CONFIRM_KEY =
 | 
				
			||||||
            new LocTextKey("sebserver.exam.proctoring.form.resetConfirm");
 | 
					            new LocTextKey("sebserver.exam.proctoring.form.resetConfirm");
 | 
				
			||||||
 | 
					    private final static LocTextKey RESET_ACTIVE_CON_KEY =
 | 
				
			||||||
 | 
					            new LocTextKey("sebserver.exam.proctoring.form.resetActive");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Function<PageAction, PageAction> settingsFunction(final PageService pageService, final boolean modifyGrant) {
 | 
					    Function<PageAction, PageAction> settingsFunction(final PageService pageService, final boolean modifyGrant) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -171,7 +173,7 @@ public class ProctoringSettingsPopup {
 | 
				
			||||||
                        pc -> new SEBProctoringPropertiesForm(
 | 
					                        pc -> new SEBProctoringPropertiesForm(
 | 
				
			||||||
                                pageService,
 | 
					                                pageService,
 | 
				
			||||||
                                pageContext,
 | 
					                                pageContext,
 | 
				
			||||||
                                resetButtonHandler));
 | 
					                                resetButtonHandler).compose(pc.getParent()));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return action;
 | 
					            return action;
 | 
				
			||||||
| 
						 | 
					@ -207,8 +209,14 @@ public class ProctoringSettingsPopup {
 | 
				
			||||||
                .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
 | 
					                .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
 | 
				
			||||||
                .call()
 | 
					                .call()
 | 
				
			||||||
                .onError(error -> {
 | 
					                .onError(error -> {
 | 
				
			||||||
                    log.error("Failed to rest proctoring settings for exam: {}", entityKey, error);
 | 
					                    if (error.getMessage().contains("active connections") ||
 | 
				
			||||||
                    pageContext.notifyUnexpectedError(error);
 | 
					                            (error.getCause() != null &&
 | 
				
			||||||
 | 
					                                    error.getCause().getMessage().contains("active connections"))) {
 | 
				
			||||||
 | 
					                        pageContext.publishInfo(RESET_ACTIVE_CON_KEY);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        log.error("Failed to rest proctoring settings for exam: {}", entityKey, error);
 | 
				
			||||||
 | 
					                        pageContext.notifyUnexpectedError(error);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }).map(settings -> true).getOr(false);
 | 
					                }).map(settings -> true).getOr(false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.Collection;
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.slf4j.Logger;
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
| 
						 | 
					@ -102,10 +103,37 @@ public interface AdditionalAttributesDAO {
 | 
				
			||||||
            final Long entityId,
 | 
					            final Long entityId,
 | 
				
			||||||
            final Map<String, String> attributes) {
 | 
					            final Map<String, String> attributes) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return saveAdditionalAttributes(type, entityId, attributes, false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Use this to save an additional attributes for a specific entity.
 | 
				
			||||||
 | 
					     * If an additional attribute with specified name already exists for the specified entity
 | 
				
			||||||
 | 
					     * this updates just the value for this additional attribute. Otherwise create a new instance
 | 
				
			||||||
 | 
					     * of additional attribute with the given data
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param type the entity type
 | 
				
			||||||
 | 
					     * @param entityId the entity identifier (primary key)
 | 
				
			||||||
 | 
					     * @param attributes Map of attributes to save for
 | 
				
			||||||
 | 
					     * @param deleteNullValues indicates if null values shall be deleted or not */
 | 
				
			||||||
 | 
					    default Result<Collection<AdditionalAttributeRecord>> saveAdditionalAttributes(
 | 
				
			||||||
 | 
					            final EntityType type,
 | 
				
			||||||
 | 
					            final Long entityId,
 | 
				
			||||||
 | 
					            final Map<String, String> attributes,
 | 
				
			||||||
 | 
					            final boolean deleteNullValues) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Result.tryCatch(() -> attributes.entrySet()
 | 
					        return Result.tryCatch(() -> attributes.entrySet()
 | 
				
			||||||
                .stream()
 | 
					                .stream()
 | 
				
			||||||
                .map(attr -> saveAdditionalAttribute(type, entityId, attr.getKey(), attr.getValue())
 | 
					                .map(attr -> {
 | 
				
			||||||
                        .onError(error -> log.warn("Failed to save additional attribute: {}", error.getMessage())))
 | 
					                    if (deleteNullValues && attr.getValue() == null) {
 | 
				
			||||||
 | 
					                        delete(type, entityId, attr.getKey());
 | 
				
			||||||
 | 
					                        return null;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        return saveAdditionalAttribute(type, entityId, attr.getKey(), attr.getValue())
 | 
				
			||||||
 | 
					                                .onError(error -> log.warn("Failed to save additional attribute: {}",
 | 
				
			||||||
 | 
					                                        error.getMessage()));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .filter(Objects::nonNull)
 | 
				
			||||||
                .flatMap(Result::skipOnError)
 | 
					                .flatMap(Result::skipOnError)
 | 
				
			||||||
                .collect(Collectors.toList()));
 | 
					                .collect(Collectors.toList()));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,6 +116,10 @@ public interface ExamAdminService {
 | 
				
			||||||
     * @return ExamProctoringService instance */
 | 
					     * @return ExamProctoringService instance */
 | 
				
			||||||
    Result<ExamProctoringService> getExamProctoringService(final Long examId);
 | 
					    Result<ExamProctoringService> getExamProctoringService(final Long examId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** This resets the proctoring settings for a given exam and stores the default settings.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param exam The exam reference
 | 
				
			||||||
 | 
					     * @return Result refer to the given exam or to an error when happened */
 | 
				
			||||||
    Result<Exam> resetProctoringSettings(Exam exam);
 | 
					    Result<Exam> resetProctoringSettings(Exam exam);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** This archives a finished exam and set it to archived state as well as the assigned
 | 
					    /** This archives a finished exam and set it to archived state as well as the assigned
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -245,6 +245,9 @@ public class ExamAdminServiceImpl implements ExamAdminService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Result<Exam> resetProctoringSettings(final Exam exam) {
 | 
					    public Result<Exam> resetProctoringSettings(final Exam exam) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // first delete all proctoring settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return getProctoringServiceSettings(exam.id)
 | 
					        return getProctoringServiceSettings(exam.id)
 | 
				
			||||||
                .map(settings -> {
 | 
					                .map(settings -> {
 | 
				
			||||||
                    ProctoringServiceSettings resetSettings;
 | 
					                    ProctoringServiceSettings resetSettings;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
import java.util.EnumSet;
 | 
					import java.util.EnumSet;
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
import java.util.Objects;
 | 
					import java.util.Objects;
 | 
				
			||||||
import java.util.function.Function;
 | 
					import java.util.function.Function;
 | 
				
			||||||
| 
						 | 
					@ -100,7 +101,6 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService {
 | 
				
			||||||
                                        ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM));
 | 
					                                        ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM));
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
                    .getOrThrow();
 | 
					                    .getOrThrow();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -118,95 +118,53 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService {
 | 
				
			||||||
                testExamProctoring(proctoringServiceSettings).getOrThrow();
 | 
					                testExamProctoring(proctoringServiceSettings).getOrThrow();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.additionalAttributesDAO.saveAdditionalAttribute(
 | 
					            final Map<String, String> attributes = new HashMap<>();
 | 
				
			||||||
                    parentEntityKey.entityType,
 | 
					            attributes.put(
 | 
				
			||||||
                    entityId,
 | 
					 | 
				
			||||||
                    ProctoringServiceSettings.ATTR_ENABLE_PROCTORING,
 | 
					                    ProctoringServiceSettings.ATTR_ENABLE_PROCTORING,
 | 
				
			||||||
                    String.valueOf(proctoringServiceSettings.enableProctoring));
 | 
					                    String.valueOf(proctoringServiceSettings.enableProctoring));
 | 
				
			||||||
 | 
					            attributes.put(
 | 
				
			||||||
            this.additionalAttributesDAO.saveAdditionalAttribute(
 | 
					 | 
				
			||||||
                    parentEntityKey.entityType,
 | 
					 | 
				
			||||||
                    entityId,
 | 
					 | 
				
			||||||
                    ProctoringServiceSettings.ATTR_SERVER_TYPE,
 | 
					                    ProctoringServiceSettings.ATTR_SERVER_TYPE,
 | 
				
			||||||
                    proctoringServiceSettings.serverType.name());
 | 
					                    proctoringServiceSettings.serverType.name());
 | 
				
			||||||
 | 
					            attributes.put(
 | 
				
			||||||
            this.additionalAttributesDAO.saveAdditionalAttribute(
 | 
					 | 
				
			||||||
                    parentEntityKey.entityType,
 | 
					 | 
				
			||||||
                    entityId,
 | 
					 | 
				
			||||||
                    ProctoringServiceSettings.ATTR_SERVER_URL,
 | 
					                    ProctoringServiceSettings.ATTR_SERVER_URL,
 | 
				
			||||||
                    StringUtils.trim(proctoringServiceSettings.serverURL));
 | 
					                    StringUtils.trim(proctoringServiceSettings.serverURL));
 | 
				
			||||||
 | 
					            attributes.put(
 | 
				
			||||||
            this.additionalAttributesDAO.saveAdditionalAttribute(
 | 
					 | 
				
			||||||
                    parentEntityKey.entityType,
 | 
					 | 
				
			||||||
                    entityId,
 | 
					 | 
				
			||||||
                    ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE,
 | 
					                    ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE,
 | 
				
			||||||
                    String.valueOf(proctoringServiceSettings.collectingRoomSize));
 | 
					                    String.valueOf(proctoringServiceSettings.collectingRoomSize));
 | 
				
			||||||
 | 
					            attributes.put(
 | 
				
			||||||
            if (StringUtils.isNotBlank(proctoringServiceSettings.appKey)) {
 | 
					                    ProctoringServiceSettings.ATTR_APP_KEY,
 | 
				
			||||||
                this.additionalAttributesDAO.saveAdditionalAttribute(
 | 
					                    StringUtils.trim(proctoringServiceSettings.appKey));
 | 
				
			||||||
                        parentEntityKey.entityType,
 | 
					            attributes.put(
 | 
				
			||||||
                        entityId,
 | 
					                    ProctoringServiceSettings.ATTR_APP_SECRET,
 | 
				
			||||||
                        ProctoringServiceSettings.ATTR_APP_KEY,
 | 
					                    encryptSecret(Utils.trim(proctoringServiceSettings.appSecret)));
 | 
				
			||||||
                        StringUtils.trim(proctoringServiceSettings.appKey));
 | 
					            attributes.put(
 | 
				
			||||||
 | 
					                    ProctoringServiceSettings.ATTR_ACCOUNT_ID,
 | 
				
			||||||
                this.additionalAttributesDAO.saveAdditionalAttribute(
 | 
					                    StringUtils.trim(proctoringServiceSettings.accountId));
 | 
				
			||||||
                        parentEntityKey.entityType,
 | 
					            attributes.put(
 | 
				
			||||||
                        entityId,
 | 
					                    ProctoringServiceSettings.ATTR_ACCOUNT_CLIENT_ID,
 | 
				
			||||||
                        ProctoringServiceSettings.ATTR_APP_SECRET,
 | 
					                    StringUtils.trim(proctoringServiceSettings.clientId));
 | 
				
			||||||
                        this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.appSecret))
 | 
					            attributes.put(
 | 
				
			||||||
                                .getOrThrow()
 | 
					                    ProctoringServiceSettings.ATTR_ACCOUNT_CLIENT_SECRET,
 | 
				
			||||||
                                .toString());
 | 
					                    encryptSecret(Utils.trim(proctoringServiceSettings.clientSecret)));
 | 
				
			||||||
            }
 | 
					            attributes.put(
 | 
				
			||||||
 | 
					                    ProctoringServiceSettings.ATTR_SDK_KEY,
 | 
				
			||||||
            if (StringUtils.isNotBlank(proctoringServiceSettings.accountId)) {
 | 
					                    StringUtils.trim(proctoringServiceSettings.sdkKey));
 | 
				
			||||||
                this.additionalAttributesDAO.saveAdditionalAttribute(
 | 
					            attributes.put(
 | 
				
			||||||
                        parentEntityKey.entityType,
 | 
					                    ProctoringServiceSettings.ATTR_SDK_SECRET,
 | 
				
			||||||
                        entityId,
 | 
					                    encryptSecret(Utils.trim(proctoringServiceSettings.sdkSecret)));
 | 
				
			||||||
                        ProctoringServiceSettings.ATTR_ACCOUNT_ID,
 | 
					            attributes.put(
 | 
				
			||||||
                        StringUtils.trim(proctoringServiceSettings.accountId));
 | 
					 | 
				
			||||||
                this.additionalAttributesDAO.saveAdditionalAttribute(
 | 
					 | 
				
			||||||
                        parentEntityKey.entityType,
 | 
					 | 
				
			||||||
                        entityId,
 | 
					 | 
				
			||||||
                        ProctoringServiceSettings.ATTR_ACCOUNT_CLIENT_ID,
 | 
					 | 
				
			||||||
                        StringUtils.trim(proctoringServiceSettings.clientId));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                this.additionalAttributesDAO.saveAdditionalAttribute(
 | 
					 | 
				
			||||||
                        parentEntityKey.entityType,
 | 
					 | 
				
			||||||
                        entityId,
 | 
					 | 
				
			||||||
                        ProctoringServiceSettings.ATTR_ACCOUNT_CLIENT_SECRET,
 | 
					 | 
				
			||||||
                        this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.clientSecret))
 | 
					 | 
				
			||||||
                                .getOrThrow()
 | 
					 | 
				
			||||||
                                .toString());
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (StringUtils.isNotBlank(proctoringServiceSettings.sdkKey)) {
 | 
					 | 
				
			||||||
                this.additionalAttributesDAO.saveAdditionalAttribute(
 | 
					 | 
				
			||||||
                        parentEntityKey.entityType,
 | 
					 | 
				
			||||||
                        entityId,
 | 
					 | 
				
			||||||
                        ProctoringServiceSettings.ATTR_SDK_KEY,
 | 
					 | 
				
			||||||
                        StringUtils.trim(proctoringServiceSettings.sdkKey));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                this.additionalAttributesDAO.saveAdditionalAttribute(
 | 
					 | 
				
			||||||
                        parentEntityKey.entityType,
 | 
					 | 
				
			||||||
                        entityId,
 | 
					 | 
				
			||||||
                        ProctoringServiceSettings.ATTR_SDK_SECRET,
 | 
					 | 
				
			||||||
                        this.cryptor.encrypt(Utils.trim(proctoringServiceSettings.sdkSecret))
 | 
					 | 
				
			||||||
                                .getOrThrow()
 | 
					 | 
				
			||||||
                                .toString());
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            this.additionalAttributesDAO.saveAdditionalAttribute(
 | 
					 | 
				
			||||||
                    parentEntityKey.entityType,
 | 
					 | 
				
			||||||
                    entityId,
 | 
					 | 
				
			||||||
                    ProctoringServiceSettings.ATTR_ENABLED_FEATURES,
 | 
					                    ProctoringServiceSettings.ATTR_ENABLED_FEATURES,
 | 
				
			||||||
                    StringUtils.join(proctoringServiceSettings.enabledFeatures, Constants.LIST_SEPARATOR));
 | 
					                    StringUtils.join(proctoringServiceSettings.enabledFeatures, Constants.LIST_SEPARATOR));
 | 
				
			||||||
 | 
					            attributes.put(
 | 
				
			||||||
            this.additionalAttributesDAO.saveAdditionalAttribute(
 | 
					 | 
				
			||||||
                    parentEntityKey.entityType,
 | 
					 | 
				
			||||||
                    entityId,
 | 
					 | 
				
			||||||
                    ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM,
 | 
					                    ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM,
 | 
				
			||||||
                    String.valueOf(proctoringServiceSettings.useZoomAppClientForCollectingRoom));
 | 
					                    String.valueOf(proctoringServiceSettings.useZoomAppClientForCollectingRoom));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.additionalAttributesDAO.saveAdditionalAttributes(
 | 
				
			||||||
 | 
					                    parentEntityKey.entityType,
 | 
				
			||||||
 | 
					                    entityId,
 | 
				
			||||||
 | 
					                    attributes,
 | 
				
			||||||
 | 
					                    true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return proctoringServiceSettings;
 | 
					            return proctoringServiceSettings;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -293,4 +251,13 @@ public class ProctoringAdminServiceImpl implements ProctoringAdminService {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String encryptSecret(final CharSequence secret) {
 | 
				
			||||||
 | 
					        if (StringUtils.isBlank(secret)) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return this.cryptor.encrypt(Utils.trim(secret))
 | 
				
			||||||
 | 
					                .getOrThrow()
 | 
				
			||||||
 | 
					                .toString();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -139,6 +139,11 @@ public interface ExamProctoringRoomService {
 | 
				
			||||||
     * @return Result refer to void or to an error when happened */
 | 
					     * @return Result refer to void or to an error when happened */
 | 
				
			||||||
    Result<Void> notifyRoomOpened(Long examId, String roomName);
 | 
					    Result<Void> notifyRoomOpened(Long examId, String roomName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Disposes all open proctoring rooms for a given exam and removes the references on the persistent storage.
 | 
				
			||||||
 | 
					     * First checks if there are active SEB client connections on for the given exam and if so, throws error.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param exam The exam to cleanup the rooms
 | 
				
			||||||
 | 
					     * @return Result refer to the given exam or to an error when happened */
 | 
				
			||||||
    Result<Exam> cleanupAllRooms(Exam exam);
 | 
					    Result<Exam> cleanupAllRooms(Exam exam);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -125,4 +125,6 @@ public interface ExamProctoringService {
 | 
				
			||||||
            RemoteProctoringRoom room,
 | 
					            RemoteProctoringRoom room,
 | 
				
			||||||
            Collection<ClientConnection> clientConnections);
 | 
					            Collection<ClientConnection> clientConnections);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void clearRestTemplateCache(final Long examId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -351,6 +351,11 @@ public class JitsiProctoringService implements ExamProctoringService {
 | 
				
			||||||
        return Result.EMPTY;
 | 
					        return Result.EMPTY;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void clearRestTemplateCache(final Long examId) {
 | 
				
			||||||
 | 
					        // Nothing to do here
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected Result<ProctoringRoomConnection> createProctoringConnection(
 | 
					    protected Result<ProctoringRoomConnection> createProctoringConnection(
 | 
				
			||||||
            final String connectionToken,
 | 
					            final String connectionToken,
 | 
				
			||||||
            final String url,
 | 
					            final String url,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,9 @@ import java.util.Base64;
 | 
				
			||||||
import java.util.Base64.Encoder;
 | 
					import java.util.Base64.Encoder;
 | 
				
			||||||
import java.util.Collection;
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.HashMap;
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.Iterator;
 | 
				
			||||||
import java.util.LinkedHashMap;
 | 
					import java.util.LinkedHashMap;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
import java.util.Objects;
 | 
					import java.util.Objects;
 | 
				
			||||||
import java.util.UUID;
 | 
					import java.util.UUID;
 | 
				
			||||||
| 
						 | 
					@ -38,11 +40,22 @@ import org.springframework.http.HttpMethod;
 | 
				
			||||||
import org.springframework.http.HttpStatus;
 | 
					import org.springframework.http.HttpStatus;
 | 
				
			||||||
import org.springframework.http.MediaType;
 | 
					import org.springframework.http.MediaType;
 | 
				
			||||||
import org.springframework.http.ResponseEntity;
 | 
					import org.springframework.http.ResponseEntity;
 | 
				
			||||||
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
 | 
					import org.springframework.http.client.SimpleClientHttpRequestFactory;
 | 
				
			||||||
 | 
					import org.springframework.security.access.AccessDeniedException;
 | 
				
			||||||
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
 | 
					import org.springframework.security.oauth2.client.OAuth2RestTemplate;
 | 
				
			||||||
import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;
 | 
					import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;
 | 
				
			||||||
import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest;
 | 
					import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.client.token.AccessTokenProvider;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.client.token.AccessTokenRequest;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.common.OAuth2AccessToken;
 | 
				
			||||||
 | 
					import org.springframework.security.oauth2.common.OAuth2RefreshToken;
 | 
				
			||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
 | 
					import org.springframework.util.LinkedMultiValueMap;
 | 
				
			||||||
 | 
					import org.springframework.util.MultiValueMap;
 | 
				
			||||||
import org.springframework.web.client.RestClientResponseException;
 | 
					import org.springframework.web.client.RestClientResponseException;
 | 
				
			||||||
import org.springframework.web.client.RestTemplate;
 | 
					import org.springframework.web.client.RestTemplate;
 | 
				
			||||||
import org.springframework.web.util.UriComponentsBuilder;
 | 
					import org.springframework.web.util.UriComponentsBuilder;
 | 
				
			||||||
| 
						 | 
					@ -196,13 +209,10 @@ public class ZoomProctoringService implements ExamProctoringService {
 | 
				
			||||||
                final ResponseEntity<String> result = newRestTemplate.testServiceConnection();
 | 
					                final ResponseEntity<String> result = newRestTemplate.testServiceConnection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (result.getStatusCode() != HttpStatus.OK) {
 | 
					                if (result.getStatusCode() != HttpStatus.OK) {
 | 
				
			||||||
                    throw new APIMessageException(Arrays.asList(
 | 
					                    throw new RuntimeException("Invalid Zoom Service response: " + result);
 | 
				
			||||||
                            APIMessage.fieldValidationError(ProctoringServiceSettings.ATTR_SERVER_URL,
 | 
					 | 
				
			||||||
                                    "proctoringSettings:serverURL:url.invalid"),
 | 
					 | 
				
			||||||
                            APIMessage.ErrorMessage.EXTERNAL_SERVICE_BINDING_ERROR.of()));
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } catch (final Exception e) {
 | 
					            } catch (final Exception e) {
 | 
				
			||||||
                log.error("Failed to access Zoom service at: {}", proctoringSettings.serverURL, e.getMessage());
 | 
					                log.error("Failed to access Zoom service at: {}", proctoringSettings.serverURL, e);
 | 
				
			||||||
                throw new APIMessageException(Arrays.asList(
 | 
					                throw new APIMessageException(Arrays.asList(
 | 
				
			||||||
                        APIMessage.fieldValidationError(ProctoringServiceSettings.ATTR_SERVER_URL,
 | 
					                        APIMessage.fieldValidationError(ProctoringServiceSettings.ATTR_SERVER_URL,
 | 
				
			||||||
                                "proctoringSettings:serverURL:url.noservice"),
 | 
					                                "proctoringSettings:serverURL:url.noservice"),
 | 
				
			||||||
| 
						 | 
					@ -655,35 +665,17 @@ public class ZoomProctoringService implements ExamProctoringService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // NOTE: following is the original code that includes the exam end time but seems to make trouble for OLAT
 | 
					        // NOTE: following is the original code that includes the exam end time but seems to make trouble for OLAT
 | 
				
			||||||
        final long nowInSeconds = Utils.getSecondsNow();
 | 
					        final long nowInSeconds = Utils.getSecondsNow();
 | 
				
			||||||
//        final long nowPlus30MinInSeconds = nowInSeconds + Utils.toSeconds(30 * Constants.MINUTE_IN_MILLIS);
 | 
					 | 
				
			||||||
        final long nowPlusOneDayInSeconds = nowInSeconds + Utils.toSeconds(Constants.DAY_IN_MILLIS);
 | 
					        final long nowPlusOneDayInSeconds = nowInSeconds + Utils.toSeconds(Constants.DAY_IN_MILLIS);
 | 
				
			||||||
//        final long nowPlusTwoDayInSeconds = nowInSeconds + Utils.toSeconds(2 * Constants.DAY_IN_MILLIS);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
//        long expTime = nowPlusOneDayInSeconds;
 | 
					 | 
				
			||||||
//        if (examProctoring.examId == null && this.examSessionService.isExamRunning(examProctoring.examId)) {
 | 
					 | 
				
			||||||
//            final Exam exam = this.examSessionService.getRunningExam(examProctoring.examId)
 | 
					 | 
				
			||||||
//                    .getOrThrow();
 | 
					 | 
				
			||||||
//            if (exam.endTime != null) {
 | 
					 | 
				
			||||||
//                expTime = Utils.toSeconds(exam.endTime.getMillis());
 | 
					 | 
				
			||||||
//            }
 | 
					 | 
				
			||||||
//        }
 | 
					 | 
				
			||||||
//        // refer to https://marketplace.zoom.us/docs/sdk/native-sdks/auth
 | 
					 | 
				
			||||||
//        // "exp": 0, //JWT expiration date (Min:1800 seconds greater than iat value, Max: 48 hours greater than iat value) in epoch format.
 | 
					 | 
				
			||||||
//        if (expTime >= nowPlusTwoDayInSeconds) {
 | 
					 | 
				
			||||||
//            expTime = nowPlusTwoDayInSeconds - 10; // Do not set to max because it is not well defined if max is included or not
 | 
					 | 
				
			||||||
//        } else if (expTime < nowPlus30MinInSeconds) {
 | 
					 | 
				
			||||||
//            expTime = nowPlusOneDayInSeconds;
 | 
					 | 
				
			||||||
//        }
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//        log.debug("**** SDK Token exp time with exam-end-time inclusion would be: {}", expTime);
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
        // NOTE: Set this to the maximum according to https://marketplace.zoom.us/docs/sdk/native-sdks/auth
 | 
					 | 
				
			||||||
        //return nowPlusTwoDayInSeconds - 1000; // Do not set to max because it is not well defined if max is included or not;
 | 
					 | 
				
			||||||
        // NOTE: It seems that since the update of web sdk to SDKToken to 1.7.0, the max is new + one day
 | 
					        // NOTE: It seems that since the update of web sdk to SDKToken to 1.7.0, the max is new + one day
 | 
				
			||||||
        return nowPlusOneDayInSeconds - 10;
 | 
					        return nowPlusOneDayInSeconds - 10;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public synchronized void clearRestTemplateCache(final Long examId) {
 | 
				
			||||||
 | 
					        this.restTemplatesCache.remove(examId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final LinkedHashMap<Long, ZoomRestTemplate> restTemplatesCache = new LinkedHashMap<>();
 | 
					    private final LinkedHashMap<Long, ZoomRestTemplate> restTemplatesCache = new LinkedHashMap<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private synchronized ZoomRestTemplate getZoomRestTemplate(final ProctoringServiceSettings proctoringSettings) {
 | 
					    private synchronized ZoomRestTemplate getZoomRestTemplate(final ProctoringServiceSettings proctoringSettings) {
 | 
				
			||||||
| 
						 | 
					@ -828,7 +820,7 @@ public class ZoomProctoringService implements ExamProctoringService {
 | 
				
			||||||
                final HttpHeaders headers = getHeaders();
 | 
					                final HttpHeaders headers = getHeaders();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
 | 
					                headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
 | 
				
			||||||
                final ResponseEntity<String> exchange = exchange(url, HttpMethod.PATCH, body, headers);
 | 
					                final ResponseEntity<String> exchange = exchange(url, HttpMethod.POST, body, headers);
 | 
				
			||||||
                return exchange;
 | 
					                return exchange;
 | 
				
			||||||
            } catch (final Exception e) {
 | 
					            } catch (final Exception e) {
 | 
				
			||||||
                log.error("Failed to apply user settings for Zoom user: {}", userId, e);
 | 
					                log.error("Failed to apply user settings for Zoom user: {}", userId, e);
 | 
				
			||||||
| 
						 | 
					@ -1006,17 +998,19 @@ public class ZoomProctoringService implements ExamProctoringService {
 | 
				
			||||||
                        .decrypt(this.credentials.secret)
 | 
					                        .decrypt(this.credentials.secret)
 | 
				
			||||||
                        .getOrThrow();
 | 
					                        .getOrThrow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                this.resource = new BaseOAuth2ProtectedResourceDetails();
 | 
					                this.resource = new ClientCredentialsResourceDetails();
 | 
				
			||||||
                this.resource.setAccessTokenUri(this.proctoringSettings.serverURL + "/oauth/token");
 | 
					                this.resource.setAccessTokenUri(this.proctoringSettings.serverURL + "/oauth/token");
 | 
				
			||||||
                this.resource.setClientId(this.credentials.clientIdAsString());
 | 
					                this.resource.setClientId(this.credentials.clientIdAsString());
 | 
				
			||||||
                this.resource.setClientSecret(decryptedSecret.toString());
 | 
					                this.resource.setClientSecret(decryptedSecret.toString());
 | 
				
			||||||
                this.resource.setGrantType("account_credentials");
 | 
					                this.resource.setGrantType("account_credentials");
 | 
				
			||||||
 | 
					                this.resource.setId(this.proctoringSettings.accountId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                final DefaultAccessTokenRequest defaultAccessTokenRequest = new DefaultAccessTokenRequest();
 | 
					                final SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
 | 
				
			||||||
                defaultAccessTokenRequest.set("account_id", this.proctoringSettings.accountId);
 | 
					                requestFactory.setOutputStreaming(false);
 | 
				
			||||||
                this.restTemplate = new OAuth2RestTemplate(
 | 
					                final OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(this.resource);
 | 
				
			||||||
                        this.resource,
 | 
					                oAuth2RestTemplate.setRequestFactory(requestFactory);
 | 
				
			||||||
                        new DefaultOAuth2ClientContext(defaultAccessTokenRequest));
 | 
					                oAuth2RestTemplate.setAccessTokenProvider(new ZoomCredentialsAccessTokenProvider());
 | 
				
			||||||
 | 
					                this.restTemplate = oAuth2RestTemplate;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1116,4 +1110,63 @@ public class ZoomProctoringService implements ExamProctoringService {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final class ZoomCredentialsAccessTokenProvider extends OAuth2AccessTokenSupport
 | 
				
			||||||
 | 
					            implements AccessTokenProvider {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public boolean supportsResource(final OAuth2ProtectedResourceDetails resource) {
 | 
				
			||||||
 | 
					            return resource instanceof ClientCredentialsResourceDetails
 | 
				
			||||||
 | 
					                    && "account_credentials".equals(resource.getGrantType());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public boolean supportsRefresh(final OAuth2ProtectedResourceDetails resource) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public OAuth2AccessToken refreshAccessToken(final OAuth2ProtectedResourceDetails resource,
 | 
				
			||||||
 | 
					                final OAuth2RefreshToken refreshToken, final AccessTokenRequest request)
 | 
				
			||||||
 | 
					                throws UserRedirectRequiredException {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @Override
 | 
				
			||||||
 | 
					        public OAuth2AccessToken obtainAccessToken(final OAuth2ProtectedResourceDetails details,
 | 
				
			||||||
 | 
					                final AccessTokenRequest request)
 | 
				
			||||||
 | 
					                throws UserRedirectRequiredException, AccessDeniedException, OAuth2AccessDeniedException {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final ClientCredentialsResourceDetails resource = (ClientCredentialsResourceDetails) details;
 | 
				
			||||||
 | 
					            return retrieveToken(request, resource, getParametersForTokenRequest(resource), new HttpHeaders());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private MultiValueMap<String, String> getParametersForTokenRequest(
 | 
				
			||||||
 | 
					                final ClientCredentialsResourceDetails resource) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            final MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
 | 
				
			||||||
 | 
					            form.set("grant_type", "account_credentials");
 | 
				
			||||||
 | 
					            form.set("account_id", resource.getId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (resource.isScoped()) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                final StringBuilder builder = new StringBuilder();
 | 
				
			||||||
 | 
					                final List<String> scope = resource.getScope();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (scope != null) {
 | 
				
			||||||
 | 
					                    final Iterator<String> scopeIt = scope.iterator();
 | 
				
			||||||
 | 
					                    while (scopeIt.hasNext()) {
 | 
				
			||||||
 | 
					                        builder.append(scopeIt.next());
 | 
				
			||||||
 | 
					                        if (scopeIt.hasNext()) {
 | 
				
			||||||
 | 
					                            builder.append(' ');
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                form.set("scope", builder.toString());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return form;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,6 +22,8 @@ import javax.validation.Valid;
 | 
				
			||||||
import org.apache.commons.lang3.StringUtils;
 | 
					import org.apache.commons.lang3.StringUtils;
 | 
				
			||||||
import org.joda.time.DateTime;
 | 
					import org.joda.time.DateTime;
 | 
				
			||||||
import org.mybatis.dynamic.sql.SqlTable;
 | 
					import org.mybatis.dynamic.sql.SqlTable;
 | 
				
			||||||
 | 
					import org.slf4j.Logger;
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory;
 | 
				
			||||||
import org.springframework.http.MediaType;
 | 
					import org.springframework.http.MediaType;
 | 
				
			||||||
import org.springframework.validation.FieldError;
 | 
					import org.springframework.validation.FieldError;
 | 
				
			||||||
import org.springframework.web.bind.annotation.PathVariable;
 | 
					import org.springframework.web.bind.annotation.PathVariable;
 | 
				
			||||||
| 
						 | 
					@ -80,6 +82,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
 | 
				
			||||||
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_ADMINISTRATION_ENDPOINT)
 | 
					@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_ADMINISTRATION_ENDPOINT)
 | 
				
			||||||
public class ExamAdministrationController extends EntityController<Exam, Exam> {
 | 
					public class ExamAdministrationController extends EntityController<Exam, Exam> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final Logger log = LoggerFactory.getLogger(ExamAdministrationController.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final ExamDAO examDAO;
 | 
					    private final ExamDAO examDAO;
 | 
				
			||||||
    private final UserDAO userDAO;
 | 
					    private final UserDAO userDAO;
 | 
				
			||||||
    private final ExamAdminService examAdminService;
 | 
					    private final ExamAdminService examAdminService;
 | 
				
			||||||
| 
						 | 
					@ -508,9 +512,16 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
 | 
				
			||||||
        return this.entityDAO
 | 
					        return this.entityDAO
 | 
				
			||||||
                .byPK(examId)
 | 
					                .byPK(examId)
 | 
				
			||||||
                .flatMap(this.examProctoringRoomService::cleanupAllRooms)
 | 
					                .flatMap(this.examProctoringRoomService::cleanupAllRooms)
 | 
				
			||||||
 | 
					                .map(exam -> {
 | 
				
			||||||
 | 
					                    this.examAdminService.getExamProctoringService(exam.id)
 | 
				
			||||||
 | 
					                            .onSuccess(service -> service.clearRestTemplateCache(exam.id))
 | 
				
			||||||
 | 
					                            .onError(error -> log.warn(
 | 
				
			||||||
 | 
					                                    "Failed to clear proctoring rest template cache for exam: {}",
 | 
				
			||||||
 | 
					                                    error.getMessage()));
 | 
				
			||||||
 | 
					                    return exam;
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
                .flatMap(this.examAdminService::resetProctoringSettings)
 | 
					                .flatMap(this.examAdminService::resetProctoringSettings)
 | 
				
			||||||
                .getOrThrow();
 | 
					                .getOrThrow();
 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // **** Proctoring
 | 
					    // **** Proctoring
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -831,7 +831,7 @@ sebserver.exam.proctoring.form.useZoomAppClient.tooltip=If this is set SEB Serve
 | 
				
			||||||
sebserver.exam.proctoring.form.saveSettings=Save Settings
 | 
					sebserver.exam.proctoring.form.saveSettings=Save Settings
 | 
				
			||||||
sebserver.exam.proctoring.form.resetSettings=Reset Settings
 | 
					sebserver.exam.proctoring.form.resetSettings=Reset Settings
 | 
				
			||||||
sebserver.exam.proctoring.form.resetConfirm=Reset will first cleanup and remove all existing proctoring rooms for this exam and then reset to default or template settings.<br/>Please make sure there are no active SEB client connections for this exam, otherwise this reset will be denied.<br/><br/>Do you want to reset proctoring for this exam now?
 | 
					sebserver.exam.proctoring.form.resetConfirm=Reset will first cleanup and remove all existing proctoring rooms for this exam and then reset to default or template settings.<br/>Please make sure there are no active SEB client connections for this exam, otherwise this reset will be denied.<br/><br/>Do you want to reset proctoring for this exam now?
 | 
				
			||||||
 | 
					sebserver.exam.proctoring.form.resetActive=There are still active SEB client connection for this exam. Please disconnect or cancel them first.<br/>This action is only possible when there are no active SEB client connection within this exam.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue