seb restriction switch, minor fixes for export

This commit is contained in:
anhefti 2019-11-05 09:15:28 +01:00
parent 2d016ed7fc
commit 78ffa7bc2c
18 changed files with 263 additions and 109 deletions

View file

@ -108,6 +108,7 @@ public final class API {
public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam"; public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam";
public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/download-config"; public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/download-config";
public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT = "/check-consistency"; public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT = "/check-consistency";
public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT = "/seb-restriction";
public static final String EXAM_INDICATOR_ENDPOINT = "/indicator"; public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";

View file

@ -67,6 +67,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingsPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingsPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SetExamSebRestriction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.ImportAsExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.ImportAsExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
@ -308,7 +309,17 @@ public class ExamForm implements TemplateComposer {
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, String.valueOf(importFromQuizData)) .withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, String.valueOf(importFromQuizData))
.withExec(this.cancelModifyFunction()) .withExec(this.cancelModifyFunction())
.publishIf(() -> !readonly); .publishIf(() -> !readonly)
.newAction(ActionDefinition.EXAM_ENABLE_SEB_RESTRICTION)
.withEntityKey(entityKey)
.withExec(action -> setSebRestriction(action, true))
.publishIf(() -> readonly && BooleanUtils.isFalse(exam.lmsSebRestriction))
.newAction(ActionDefinition.EXAM_DISABLE_SEB_RESTRICTION)
.withEntityKey(entityKey)
.withExec(action -> setSebRestriction(action, false))
.publishIf(() -> readonly && BooleanUtils.isTrue(exam.lmsSebRestriction));
// additional data in read-only view // additional data in read-only view
if (readonly && !importFromQuizData) { if (readonly && !importFromQuizData) {
@ -491,6 +502,20 @@ public class ExamForm implements TemplateComposer {
message)); message));
} }
private PageAction setSebRestriction(final PageAction action, final boolean sebRestriction) {
this.restService.getBuilder(SetExamSebRestriction.class)
.withURIVariable(
API.PARAM_MODEL_ID,
action.pageContext().getAttribute(AttributeKeys.ENTITY_ID))
.withQueryParam(
Domain.EXAM.ATTR_LMS_SEB_RESTRICTION,
sebRestriction ? Constants.TRUE_STRING : Constants.FALSE_STRING)
.call()
.onError(t -> action.pageContext().notifyError(t));
return action;
}
private PageAction viewExamConfigPageAction(final EntityTable<ExamConfigurationMap> table) { private PageAction viewExamConfigPageAction(final EntityTable<ExamConfigurationMap> table) {
return this.pageService.pageActionBuilder(table.getPageContext() return this.pageService.pageActionBuilder(table.getPageContext()

View file

@ -237,6 +237,7 @@ public class SebExamConfigPropForm implements TemplateComposer {
urlLauncher.openURL(downloadURL); urlLauncher.openURL(downloadURL);
return action; return action;
}) })
.noEventPropagation()
.publishIf(() -> modifyGrant && isReadonly) .publishIf(() -> modifyGrant && isReadonly)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_GET_CONFIG_KEY) .newAction(ActionDefinition.SEB_EXAM_CONFIG_GET_CONFIG_KEY)

View file

@ -226,6 +226,16 @@ public enum ActionDefinition {
ImageIcon.TOGGLE_ON, ImageIcon.TOGGLE_ON,
PageStateDefinitionImpl.EXAM_VIEW, PageStateDefinitionImpl.EXAM_VIEW,
ActionCategory.FORM), ActionCategory.FORM),
EXAM_ENABLE_SEB_RESTRICTION(
new LocTextKey("sebserver.exam.action.sebrestriction.enable"),
ImageIcon.TOGGLE_OFF,
PageStateDefinitionImpl.EXAM_VIEW,
ActionCategory.FORM),
EXAM_DISABLE_SEB_RESTRICTION(
new LocTextKey("sebserver.exam.action.deasebrestriction.disable"),
ImageIcon.TOGGLE_ON,
PageStateDefinitionImpl.EXAM_VIEW,
ActionCategory.FORM),
EXAM_CONFIGURATION_NEW( EXAM_CONFIGURATION_NEW(
new LocTextKey("sebserver.exam.configuration.action.list.new"), new LocTextKey("sebserver.exam.configuration.action.list.new"),

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class SetExamSebRestriction extends RestCall<Exam> {
public SetExamSebRestriction() {
super(new TypeKey<>(
CallType.SAVE,
EntityType.EXAM,
new TypeReference<Exam>() {
}),
HttpMethod.PATCH,
MediaType.APPLICATION_JSON_UTF8,
API.EXAM_ADMINISTRATION_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT);
}
}

View file

@ -54,4 +54,6 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
Result<Boolean> upToDate(Long examId, String lastUpdate); Result<Boolean> upToDate(Long examId, String lastUpdate);
Result<Exam> setSebRestriction(Long id, boolean sebRestriction);
} }

View file

@ -190,6 +190,24 @@ public class ExamDAOImpl implements ExamDAO {
.onError(TransactionHandler::rollback); .onError(TransactionHandler::rollback);
} }
@Override
@Transactional
public Result<Exam> setSebRestriction(final Long examId, final boolean sebRestriction) {
return Result.tryCatch(() -> {
final ExamRecord examRecord = new ExamRecord(
examId,
null, null, null, null, null, null, null, null, null,
BooleanUtils.toInteger(sebRestriction),
null, null, null);
this.examRecordMapper.updateByPrimaryKeySelective(examRecord);
return this.examRecordMapper.selectByPrimaryKey(examId);
})
.flatMap(this::toDomainModel)
.onError(TransactionHandler::rollback);
}
@Override @Override
@Transactional @Transactional
public Result<Exam> createNew(final Exam exam) { public Result<Exam> createNew(final Exam exam) {

View file

@ -8,43 +8,57 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SebRestrictionData;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class OpenEdxSebClientRestriction { public class OpenEdxSebClientRestriction {
@JsonProperty("CONFIG_KEYS") private static final String ATTR_USER_BANNING_ENABLED = "USER_BANNING_ENABLED";
private static final String ATTR_SEB_PERMISSION_COMPONENTS = "SEB_PERMISSION_COMPONENTS";
private static final String ATTR_BLACKLIST_CHAPTERS = "BLACKLIST_CHAPTERS";
private static final String ATTR_WHITELIST_PATHS = "WHITELIST_PATHS";
private static final String ATTR_BROWSER_KEYS = "BROWSER_KEYS";
private static final String ATTR_CONFIG_KEYS = "CONFIG_KEYS";
@JsonProperty(ATTR_CONFIG_KEYS)
public final Collection<String> configKeys; public final Collection<String> configKeys;
@JsonProperty("BROWSER_KEYS") @JsonProperty(ATTR_BROWSER_KEYS)
public final Collection<String> browserExamKeys; public final Collection<String> browserExamKeys;
@JsonProperty("WHITELIST_PATHS") @JsonProperty(ATTR_WHITELIST_PATHS)
public final Collection<String> whiteListPaths; public final Collection<String> whiteListPaths;
@JsonProperty("BLACKLIST_CHAPTERS") @JsonProperty(ATTR_BLACKLIST_CHAPTERS)
public final Collection<String> blacklistChapters; public final Collection<String> blacklistChapters;
@JsonProperty("SEB_PERMISSION_COMPONENTS") @JsonProperty(ATTR_SEB_PERMISSION_COMPONENTS)
public final Collection<String> permissionComponents; public final Collection<String> permissionComponents;
@JsonProperty("USER_BANNING_ENABLED") @JsonProperty(ATTR_USER_BANNING_ENABLED)
public final boolean banningEnabled; public final boolean banningEnabled;
@JsonCreator @JsonCreator
protected OpenEdxSebClientRestriction( public OpenEdxSebClientRestriction(
@JsonProperty("CONFIG_KEYS") final Collection<String> configKeys, @JsonProperty(ATTR_CONFIG_KEYS) final Collection<String> configKeys,
@JsonProperty("BROWSER_KEYS") final Collection<String> browserExamKeys, @JsonProperty(ATTR_BROWSER_KEYS) final Collection<String> browserExamKeys,
@JsonProperty("WHITELIST_PATHS") final Collection<String> whiteListPaths, @JsonProperty(ATTR_WHITELIST_PATHS) final Collection<String> whiteListPaths,
@JsonProperty("BLACKLIST_CHAPTERS") final Collection<String> blacklistChapters, @JsonProperty(ATTR_BLACKLIST_CHAPTERS) final Collection<String> blacklistChapters,
@JsonProperty("SEB_PERMISSION_COMPONENTS") final Collection<String> permissionComponents, @JsonProperty(ATTR_SEB_PERMISSION_COMPONENTS) final Collection<String> permissionComponents,
@JsonProperty("USER_BANNING_ENABLED") final boolean banningEnabled) { @JsonProperty(ATTR_USER_BANNING_ENABLED) final boolean banningEnabled) {
this.configKeys = Utils.immutableCollectionOf(configKeys); this.configKeys = Utils.immutableCollectionOf(configKeys);
this.browserExamKeys = Utils.immutableCollectionOf(browserExamKeys); this.browserExamKeys = Utils.immutableCollectionOf(browserExamKeys);
@ -54,6 +68,37 @@ public class OpenEdxSebClientRestriction {
this.banningEnabled = banningEnabled; this.banningEnabled = banningEnabled;
} }
public OpenEdxSebClientRestriction(final SebRestrictionData data) {
this.configKeys = Utils.immutableCollectionOf(data.configKeys);
this.browserExamKeys = Utils.immutableCollectionOf(data.browserExamKeys);
final String whiteListPaths = data.additionalAttributes.get(ATTR_WHITELIST_PATHS);
if (StringUtils.isNotBlank(whiteListPaths)) {
this.whiteListPaths = Utils.immutableCollectionOf(Arrays.asList(
StringUtils.split(whiteListPaths, Constants.LIST_SEPARATOR)));
} else {
this.whiteListPaths = Collections.emptyList();
}
final String blacklistChapters = data.additionalAttributes.get(ATTR_BLACKLIST_CHAPTERS);
if (StringUtils.isNotBlank(blacklistChapters)) {
this.blacklistChapters = Utils.immutableCollectionOf(Arrays.asList(
StringUtils.split(blacklistChapters, Constants.LIST_SEPARATOR)));
} else {
this.blacklistChapters = Collections.emptyList();
}
final String permissionComponents = data.additionalAttributes.get(ATTR_SEB_PERMISSION_COMPONENTS);
if (StringUtils.isNotBlank(permissionComponents)) {
this.permissionComponents = Utils.immutableCollectionOf(Arrays.asList(
StringUtils.split(permissionComponents, Constants.LIST_SEPARATOR)));
} else {
this.permissionComponents = Collections.emptyList();
}
this.banningEnabled = BooleanUtils.toBoolean(data.additionalAttributes.get(ATTR_USER_BANNING_ENABLED));
}
public Collection<String> getConfigKeys() { public Collection<String> getConfigKeys() {
return this.configKeys; return this.configKeys;
} }

View file

@ -174,7 +174,7 @@ public class TableConverter implements AttributeValueConverter {
final ConfigurationAttribute attr = attrItr.next(); final ConfigurationAttribute attr = attrItr.next();
ConfigurationValue value = rowValues.stream() ConfigurationValue value = rowValues.stream()
.filter(val -> attr.id.equals(val.attributeId)) .filter(val -> val != null && attr.id.equals(val.attributeId))
.findFirst() .findFirst()
.orElse(null); .orElse(null);

View file

@ -36,7 +36,7 @@ public interface ExamSessionService {
* @param examId the identifier of the Exam to check * @param examId the identifier of the Exam to check
* @return Result of one APIMessage per consistency check if the check failed. An empty Collection of everything is * @return Result of one APIMessage per consistency check if the check failed. An empty Collection of everything is
* okay. */ * okay. */
Result<Collection<APIMessage>> checkRunningExamConsystency(Long examId); Result<Collection<APIMessage>> checkRunningExamConsistency(Long examId);
/** Checks if a specified Exam has at least a default SEB Exam configuration attached. /** Checks if a specified Exam has at least a default SEB Exam configuration attached.
* *

View file

@ -10,7 +10,6 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Objects;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -130,38 +129,23 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
} }
// generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key) // generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key)
final Collection<Long> updatedExams = updateLmsSebRestriction(exams) for (final Exam exam : exams) {
.stream() if (exam.getStatus() == ExamStatus.RUNNING) {
.map(Result::get) this.updateSebClientRestriction(exam)
.filter(Objects::nonNull) .onError(t -> log.error("Failed to update SEB Client restriction for Exam: {}", exam, t));
.map(Exam::getId) }
.collect(Collectors.toList());
if (log.isDebugEnabled()) {
log.debug("Successfully updated ConfigKey for Exams: {}", updatedExams);
} }
// evict each Exam from cache and release the update-lock on DB // evict each Exam from cache and release the update-lock on DB
final Collection<Long> evictedExams = evictFromCache(exams) for (final Exam exam : exams) {
.stream() this.examSessionService.flushCache(exam)
.filter(Result::hasValue) .onError(t -> log.error("Failed to flush Exam from cache: {}", exam, t));
.map(Result::get)
.map(Exam::getId)
.collect(Collectors.toList());
if (log.isDebugEnabled()) {
log.debug("Successfully evicted Exams from cache: {}", evictedExams);
} }
// release the update-locks on involved exams // release the update-locks on involved exams
final Collection<Long> releasedLocks = releaseUpdateLocks(examIdsFirstCheck, updateId) for (final Long examId : examIdsFirstCheck) {
.stream() this.examDAO.releaseLock(examId, updateId)
.map(Result::getOrThrow) .onError(t -> log.error("Failed to release lock for Exam: {}", examId, t));
.map(Exam::getId)
.collect(Collectors.toList());
if (log.isDebugEnabled()) {
log.debug("Successfully released update-locks on Exams: {}", releasedLocks);
} }
return examIdsFirstCheck; return examIdsFirstCheck;
@ -296,43 +280,6 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
return this.examUpdateHandler.releaseSebClientRestriction(exam); return this.examUpdateHandler.releaseSebClientRestriction(exam);
} }
private void checkIntegrityDoubleCheck(
final Collection<Long> examIdsFirstCheck,
final Collection<Long> examIdsSecondCheck) {
if (examIdsFirstCheck.size() != examIdsSecondCheck.size()) {
throw new IllegalStateException("Running Exam integrity check missmatch. examIdsFirstCheck: "
+ examIdsFirstCheck + " examIdsSecondCheck: " + examIdsSecondCheck);
}
}
private Collection<Result<Exam>> lockForUpdate(final Collection<Long> examIds, final String update) {
return examIds.stream()
.map(id -> this.examDAO.placeLock(id, update))
.collect(Collectors.toList());
}
private Collection<Result<Exam>> releaseUpdateLocks(final Collection<Long> examIds, final String update) {
return examIds.stream()
.map(id -> this.examDAO.releaseLock(id, update))
.collect(Collectors.toList());
}
private Collection<Result<Exam>> updateLmsSebRestriction(final Collection<Exam> exams) {
return exams
.stream()
.filter(exam -> exam.getStatus() == ExamStatus.RUNNING)
.map(this::updateSebClientRestriction)
.collect(Collectors.toList());
}
private Collection<Result<Exam>> evictFromCache(final Collection<Exam> exams) {
return exams
.stream()
.map(this.examSessionService::flushCache)
.collect(Collectors.toList());
}
@Override @Override
public Result<Collection<Long>> checkRunningExamIntegrity(final Long configurationNodeId) { public Result<Collection<Long>> checkRunningExamIntegrity(final Long configurationNodeId) {
final Collection<Long> involvedExams = this.examConfigurationMapDAO final Collection<Long> involvedExams = this.examConfigurationMapDAO
@ -397,4 +344,20 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
return false; return false;
} }
private void checkIntegrityDoubleCheck(
final Collection<Long> examIdsFirstCheck,
final Collection<Long> examIdsSecondCheck) {
if (examIdsFirstCheck.size() != examIdsSecondCheck.size()) {
throw new IllegalStateException("Running Exam integrity check missmatch. examIdsFirstCheck: "
+ examIdsFirstCheck + " examIdsSecondCheck: " + examIdsSecondCheck);
}
}
private Collection<Result<Exam>> lockForUpdate(final Collection<Long> examIds, final String update) {
return examIds.stream()
.map(id -> this.examDAO.placeLock(id, update))
.collect(Collectors.toList());
}
} }

View file

@ -78,7 +78,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
} }
@Override @Override
public Result<Collection<APIMessage>> checkRunningExamConsystency(final Long examId) { public Result<Collection<APIMessage>> checkRunningExamConsistency(final Long examId) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
final Collection<APIMessage> result = new ArrayList<>(); final Collection<APIMessage> result = new ArrayList<>();

View file

@ -120,10 +120,6 @@ class ExamUpdateHandler {
return Result.of(exam); return Result.of(exam);
} }
if (log.isDebugEnabled()) {
log.debug("Apply SEB Client restrictions for exam: {}", exam);
}
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
final SebRestrictionData sebRestrictionData = createSebRestrictionData(exam); final SebRestrictionData sebRestrictionData = createSebRestrictionData(exam);
@ -153,10 +149,6 @@ class ExamUpdateHandler {
final SebRestrictionData sebRestrictionData = createSebRestrictionData(exam); final SebRestrictionData sebRestrictionData = createSebRestrictionData(exam);
if (log.isDebugEnabled()) {
log.debug("Update SEB Client restrictions for exam: {}", exam);
}
return this.lmsAPIService return this.lmsAPIService
.getLmsAPITemplate(exam.lmsSetupId) .getLmsAPITemplate(exam.lmsSetupId)
.flatMap(lmsTemplate -> lmsTemplate.updateSebClientRestriction(sebRestrictionData)) .flatMap(lmsTemplate -> lmsTemplate.updateSebClientRestriction(sebRestrictionData))
@ -176,19 +168,6 @@ class ExamUpdateHandler {
.flatMap(template -> template.releaseSebClientRestriction(exam)); .flatMap(template -> template.releaseSebClientRestriction(exam));
} }
// @Transactional
// public <T> Result<T> updateExamConfigurationMappingChange(
// final ExamConfigurationMap mapping,
// final Function<ExamConfigurationMap, Result<T>> changeAction,
// final Exam exam) {
//
// return Result.tryCatch(() -> {
//
// return result;
// })
// .onError(TransactionHandler::rollback);
// }
private SebRestrictionData createSebRestrictionData(final Exam exam) { private SebRestrictionData createSebRestrictionData(final Exam exam) {
final Collection<String> configKeys = this.sebExamConfigService final Collection<String> configKeys = this.sebExamConfigService
.generateConfigKeys(exam.institutionId, exam.id) .generateConfigKeys(exam.institutionId, exam.id)
@ -206,7 +185,7 @@ class ExamUpdateHandler {
exam, exam,
configKeys, configKeys,
browserExamKeys, browserExamKeys,
// TODO when we have more restriction details available form the Exam, put it to the map // TODO when we have more restriction details available from the Exam, put it to the map
Collections.emptyMap()); Collections.emptyMap());
return sebRestrictionData; return sebRestrictionData;
} }

View file

@ -19,6 +19,7 @@ import java.util.stream.Collectors;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlTable; import org.mybatis.dynamic.sql.SqlTable;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -60,6 +61,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebExamConfigService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
@ -75,6 +77,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
private final LmsAPIService lmsAPIService; private final LmsAPIService lmsAPIService;
private final SebExamConfigService sebExamConfigService; private final SebExamConfigService sebExamConfigService;
private final ExamSessionService examSessionService; private final ExamSessionService examSessionService;
private final ExamConfigUpdateService examConfigUpdateService;
public ExamAdministrationController( public ExamAdministrationController(
final AuthorizationService authorization, final AuthorizationService authorization,
@ -86,7 +89,8 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
final LmsAPIService lmsAPIService, final LmsAPIService lmsAPIService,
final UserDAO userDAO, final UserDAO userDAO,
final SebExamConfigService sebExamConfigService, final SebExamConfigService sebExamConfigService,
final ExamSessionService examSessionService) { final ExamSessionService examSessionService,
final ExamConfigUpdateService examConfigUpdateService) {
super(authorization, super(authorization,
bulkActionService, bulkActionService,
@ -100,6 +104,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
this.lmsAPIService = lmsAPIService; this.lmsAPIService = lmsAPIService;
this.sebExamConfigService = sebExamConfigService; this.sebExamConfigService = sebExamConfigService;
this.examSessionService = examSessionService; this.examSessionService = examSessionService;
this.examConfigUpdateService = examConfigUpdateService;
} }
@Override @Override
@ -197,9 +202,39 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
+ API.EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT, + API.EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT,
method = RequestMethod.GET, method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE) produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Collection<APIMessage> checkExamConsistency(@PathVariable final Long modelId) { public Collection<APIMessage> checkExamConsistency(
@PathVariable final Long modelId,
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
checkModifyPrivilege(institutionId);
return this.examSessionService return this.examSessionService
.checkRunningExamConsystency(modelId) .checkRunningExamConsistency(modelId)
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT,
method = RequestMethod.PATCH,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Exam switchSebRestriction(
@PathVariable final Long modelId,
@RequestParam(name = Domain.EXAM.ATTR_LMS_SEB_RESTRICTION, required = true) final boolean sebRestriction,
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
checkModifyPrivilege(institutionId);
return this.entityDAO.byPK(modelId)
.flatMap(this.authorization::checkModify)
.flatMap(this::checkNoActiveSebClientConnections)
.flatMap(exam -> this.setSebRestriction(exam, sebRestriction))
.flatMap(this.userActivityLogDAO::logModify)
.getOrThrow(); .getOrThrow();
} }
@ -291,4 +326,35 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
return exam; return exam;
} }
private Result<Exam> checkNoActiveSebClientConnections(final Exam exam) {
if (this.examConfigUpdateService.hasActiveSebClientConnections(exam.id)) {
return Result.ofError(new APIMessageException(
APIMessage.ErrorMessage.INTEGRITY_VALIDATION
.of("Exam currently has active SEB Client connections.")));
}
return Result.of(exam);
}
private Result<Exam> setSebRestriction(final Exam exam, final boolean sebRestriction) {
return Result.tryCatch(() -> {
if (BooleanUtils.toBoolean(exam.lmsSebRestriction) == sebRestriction) {
return exam;
}
final Exam examUpdate = this.examDAO.setSebRestriction(exam.id, sebRestriction)
.getOrThrow();
if (sebRestriction) {
this.examConfigUpdateService.applySebClientRestriction(examUpdate)
.getOrThrow();
} else {
this.examConfigUpdateService.releaseSebClientRestriction(examUpdate)
.getOrThrow();
}
return examUpdate;
});
}
} }

View file

@ -262,7 +262,7 @@ INSERT IGNORE INTO configuration_attribute VALUES
(519, 'enableF11', 'CHECKBOX', null, null, null, null, 'false'), (519, 'enableF11', 'CHECKBOX', null, null, null, null, 'false'),
(520, 'enableF12', 'CHECKBOX', null, null, null, null, 'false'), (520, 'enableF12', 'CHECKBOX', null, null, null, null, 'false'),
(800, 'browserMessagingSocket', 'TEXT_FIELD', null, null, null, null, 'ws:\\localhost:8706'), (800, 'browserMessagingSocket', 'TEXT_FIELD', null, null, null, null, 'ws://localhost:8706'),
(801, 'browserMessagingPingTime', 'INTEGER', null, null, null, null, '120000'), (801, 'browserMessagingPingTime', 'INTEGER', null, null, null, null, '120000'),
(802, 'allowPreferencesWindow', 'CHECKBOX', null, null, null, null, 'true'), (802, 'allowPreferencesWindow', 'CHECKBOX', null, null, null, null, 'true'),
(803, 'useAsymmetricOnlyEncryption', 'CHECKBOX', null, null, null, null, 'false'), (803, 'useAsymmetricOnlyEncryption', 'CHECKBOX', null, null, null, null, 'false'),

View file

@ -235,7 +235,7 @@ INSERT IGNORE INTO configuration_attribute VALUES
(519, 'enableF11', 'CHECKBOX', null, null, null, null, 'false'), (519, 'enableF11', 'CHECKBOX', null, null, null, null, 'false'),
(520, 'enableF12', 'CHECKBOX', null, null, null, null, 'false'), (520, 'enableF12', 'CHECKBOX', null, null, null, null, 'false'),
(800, 'browserMessagingSocket', 'TEXT_FIELD', null, null, null, null, 'ws:\\localhost:8706'), (800, 'browserMessagingSocket', 'TEXT_FIELD', null, null, null, null, 'ws://localhost:8706'),
(801, 'browserMessagingPingTime', 'INTEGER', null, null, null, null, '120000'), (801, 'browserMessagingPingTime', 'INTEGER', null, null, null, null, '120000'),
(802, 'allowPreferencesWindow', 'CHECKBOX', null, null, null, null, 'true'), (802, 'allowPreferencesWindow', 'CHECKBOX', null, null, null, null, 'true'),
(803, 'useAsymmetricOnlyEncryption', 'CHECKBOX', null, null, null, null, 'false'), (803, 'useAsymmetricOnlyEncryption', 'CHECKBOX', null, null, null, null, 'false'),

View file

@ -301,6 +301,8 @@ sebserver.exam.action.import=Import From Quizzes
sebserver.exam.action.save=Save Exam sebserver.exam.action.save=Save Exam
sebserver.exam.action.activate=Activate Exam sebserver.exam.action.activate=Activate Exam
sebserver.exam.action.deactivate=Deactivate Exam sebserver.exam.action.deactivate=Deactivate Exam
sebserver.exam.action.sebrestriction.enable=Enable SEB Restriction
sebserver.exam.action.sebrestriction.disable=Disable SEB Restriction
sebserver.exam.info.pleaseSelect=Please Select an Exam first sebserver.exam.info.pleaseSelect=Please Select an Exam first

View file

@ -243,7 +243,7 @@ INSERT IGNORE INTO configuration_attribute VALUES
(519, 'enableF11', 'CHECKBOX', null, null, null, null, 'false'), (519, 'enableF11', 'CHECKBOX', null, null, null, null, 'false'),
(520, 'enableF12', 'CHECKBOX', null, null, null, null, 'false'), (520, 'enableF12', 'CHECKBOX', null, null, null, null, 'false'),
(800, 'browserMessagingSocket', 'TEXT_FIELD', null, null, null, null, 'ws:\\localhost:8706'), (800, 'browserMessagingSocket', 'TEXT_FIELD', null, null, null, null, 'ws://localhost:8706'),
(801, 'browserMessagingPingTime', 'INTEGER', null, null, null, null, '120000'), (801, 'browserMessagingPingTime', 'INTEGER', null, null, null, null, '120000'),
(802, 'allowPreferencesWindow', 'CHECKBOX', null, null, null, null, 'true'), (802, 'allowPreferencesWindow', 'CHECKBOX', null, null, null, null, 'true'),
(803, 'useAsymmetricOnlyEncryption', 'CHECKBOX', null, null, null, null, 'false'), (803, 'useAsymmetricOnlyEncryption', 'CHECKBOX', null, null, null, null, 'false'),