SEBSERV-482 implementation no testing yet

This commit is contained in:
anhefti 2024-01-10 16:38:56 +01:00
parent 74d4b1ff7d
commit ac21400388
32 changed files with 428 additions and 171 deletions

View file

@ -27,7 +27,7 @@ import ch.ethz.seb.sebserver.gbl.profile.ProdWebServiceProfile;
/** SEB-Server (Safe Exam Browser Server) is a server component to maintain and support
* Exams running with SEB (Safe Exam Browser). TODO add link(s)
*
* <p>
* SEB-Server uses Spring Boot as main framework is divided into two main components,
* a webservice component that implements the business logic, persistence management
* and defines a REST API to expose the services over HTTP. The second component is a
@ -49,7 +49,7 @@ public class SEBServer {
}
/*
* Add an additional redirect Connector on http port to redirect all http calls
* Add a redirect Connector on http port to redirect all http calls
* to https.
*
* NOTE: This works with TomcatServletWebServerFactory and embedded tomcat.

View file

@ -39,25 +39,10 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
public final class Exam implements GrantEntity {
public static final Exam EMPTY_EXAM = new Exam(
-1L,
-1L,
-1L,
Constants.EMPTY_NOTE,
false,
Constants.EMPTY_NOTE,
null,
null,
ExamType.UNDEFINED,
null,
null,
ExamStatus.FINISHED,
Boolean.FALSE,
null,
Boolean.FALSE,
null,
null,
null,
null);
-1L, -1L, -1L, Constants.EMPTY_NOTE, false, Constants.EMPTY_NOTE,
null, null, ExamType.UNDEFINED, null, null, ExamStatus.FINISHED,
null, Boolean.FALSE, null, Boolean.FALSE, null,
null, null, null);
public static final String FILTER_ATTR_TYPE = "type";
public static final String FILTER_ATTR_STATUS = "status";
@ -130,6 +115,9 @@ public final class Exam implements GrantEntity {
@JsonProperty(EXAM.ATTR_STATUS)
public final ExamStatus status;
@JsonProperty(EXAM.ATTR_QUIT_PASSWORD)
public final String quitPassword;
@JsonProperty(EXAM.ATTR_LMS_SEB_RESTRICTION)
public final Boolean sebRestriction;
@ -170,6 +158,7 @@ public final class Exam implements GrantEntity {
@JsonProperty(EXAM.ATTR_OWNER) final String owner,
@JsonProperty(EXAM.ATTR_SUPPORTER) final Collection<String> supporter,
@JsonProperty(EXAM.ATTR_STATUS) final ExamStatus status,
@JsonProperty(EXAM.ATTR_QUIT_PASSWORD) final String quitPassword,
@JsonProperty(EXAM.ATTR_LMS_SEB_RESTRICTION) final Boolean sebRestriction,
@JsonProperty(EXAM.ATTR_BROWSER_KEYS) final String browserExamKeys,
@JsonProperty(EXAM.ATTR_ACTIVE) final Boolean active,
@ -189,6 +178,7 @@ public final class Exam implements GrantEntity {
this.type = type;
this.owner = owner;
this.status = (status != null) ? status : getStatusFromDate(startTime, endTime);
this.quitPassword = quitPassword;
this.sebRestriction = sebRestriction;
this.browserExamKeys = browserExamKeys;
this.active = (active != null) ? active : Boolean.TRUE;
@ -219,6 +209,7 @@ public final class Exam implements GrantEntity {
this.type = postMap.getEnum(EXAM.ATTR_TYPE, ExamType.class, ExamType.UNDEFINED);
this.owner = postMap.getString(EXAM.ATTR_OWNER);
this.status = postMap.getEnum(EXAM.ATTR_STATUS, ExamStatus.class, getStatusFromDate(this.startTime, this.endTime));
this.quitPassword = postMap.getString(EXAM.ATTR_QUIT_PASSWORD);
this.sebRestriction = null;
this.browserExamKeys = null;
this.active = postMap.getBoolean(EXAM.ATTR_ACTIVE);
@ -262,6 +253,7 @@ public final class Exam implements GrantEntity {
EXAM.ATTR_STATUS,
ExamStatus.class,
getStatusFromDate(this.startTime, this.endTime));
this.quitPassword = mapper.getString(EXAM.ATTR_QUIT_PASSWORD);
this.sebRestriction = null;
this.browserExamKeys = mapper.getString(EXAM.ATTR_BROWSER_KEYS);
this.active = mapper.getBoolean(EXAM.ATTR_ACTIVE);
@ -389,6 +381,10 @@ public final class Exam implements GrantEntity {
return this.status;
}
public String getQuitPassword() {
return quitPassword;
}
public String getBrowserExamKeys() {
return this.browserExamKeys;
}

View file

@ -36,6 +36,7 @@ public class ExamTemplate implements GrantEntity {
public static final String FILTER_ATTR_EXAM_TYPE = EXAM_TEMPLATE.ATTR_EXAM_TYPE;
public static final String ATTR_CLIENT_GROUP_TEMPLATES = "CLIENT_GROUP_TEMPLATES";
public static final String ATTR_EXAM_ATTRIBUTES = "EXAM_ATTRIBUTES";
public static final String ATTR_QUIT_PASSWORD = "quitPassword";
@JsonProperty(EXAM_TEMPLATE.ATTR_ID)
public final Long id;
@ -243,8 +244,7 @@ public class ExamTemplate implements GrantEntity {
}
public static ExamTemplate createNew(final Long institutionId) {
return new ExamTemplate(null, institutionId, null, null, ExamType.UNDEFINED, null, null, false, null, null,
null);
return new ExamTemplate(null, institutionId, null, null, ExamType.UNDEFINED, null, null, false, null, null, null);
}
}

View file

@ -23,6 +23,7 @@ import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi;
import org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.BCPKCS12KeyStore;
import org.slf4j.Logger;
@ -132,6 +133,19 @@ public class Cryptor {
});
}
public Result<CharSequence> encryptCheckAlreadyEncrypted(final CharSequence text) {
return Result.tryCatch(() -> {
// try to decrypt to check if it is already encrypted
final Result<CharSequence> decryption = this.decrypt(text);
if (decryption.hasError()) {
return encrypt(text).getOrThrow();
} else {
return text;
}
});
}
public static Result<CharSequence> decrypt(final CharSequence cipher, final CharSequence secret) {
return Result.tryCatch(() -> {
if (cipher == null) {
@ -183,4 +197,5 @@ public class Cryptor {
});
}
}

View file

@ -83,61 +83,33 @@ public class ExamForm implements TemplateComposer {
protected static final String ATTR_EDITABLE = "ATTR_EDITABLE";
protected static final String ATTR_EXAM_STATUS = "ATTR_EXAM_STATUS";
public static final LocTextKey EXAM_FORM_TITLE_KEY =
new LocTextKey("sebserver.exam.form.title");
public static final LocTextKey EXAM_FORM_TITLE_IMPORT_KEY =
new LocTextKey("sebserver.exam.form.title.import");
private static final LocTextKey FORM_SUPPORTER_TEXT_KEY =
new LocTextKey("sebserver.exam.form.supporter");
private static final LocTextKey FORM_STATUS_TEXT_KEY =
new LocTextKey("sebserver.exam.form.status");
private static final LocTextKey FORM_TYPE_TEXT_KEY =
new LocTextKey("sebserver.exam.form.type");
private static final LocTextKey FORM_END_TIME_TEXT_KEY =
new LocTextKey("sebserver.exam.form.endtime");
private static final LocTextKey FORM_START_TIME_TEXT_KEY =
new LocTextKey("sebserver.exam.form.starttime");
private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
new LocTextKey("sebserver.exam.form.description");
private static final LocTextKey FORM_NAME_TEXT_KEY =
new LocTextKey("sebserver.exam.form.name");
private static final LocTextKey FORM_QUIZ_ID_TEXT_KEY =
new LocTextKey("sebserver.exam.form.quizid");
private static final LocTextKey FORM_QUIZ_URL_TEXT_KEY =
new LocTextKey("sebserver.exam.form.quizurl");
private static final LocTextKey FORM_LMSSETUP_TEXT_KEY =
new LocTextKey("sebserver.exam.form.lmssetup");
private final static LocTextKey ACTION_MESSAGE_SEB_RESTRICTION_RELEASE =
new LocTextKey("sebserver.exam.action.sebrestriction.release.confirm");
private static final LocTextKey FORM_EXAM_TEMPLATE_TEXT_KEY =
new LocTextKey("sebserver.exam.form.examTemplate");
private static final LocTextKey FORM_EXAM_TEMPLATE_ERROR =
new LocTextKey("sebserver.exam.form.examTemplate.error");
private static final LocTextKey EXAM_ARCHIVE_CONFIRM =
new LocTextKey("sebserver.exam.action.archive.confirm");
private final static LocTextKey CONSISTENCY_MESSAGE_TITLE =
new LocTextKey("sebserver.exam.consistency.title");
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_SUPPORTER =
new LocTextKey("sebserver.exam.consistency.missing-supporter");
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_INDICATOR =
new LocTextKey("sebserver.exam.consistency.missing-indicator");
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_CONFIG =
new LocTextKey("sebserver.exam.consistency.missing-config");
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_SEB_RESTRICTION =
new LocTextKey("sebserver.exam.consistency.missing-seb-restriction");
private final static LocTextKey CONSISTENCY_MESSAGE_VALIDATION_LMS_CONNECTION =
new LocTextKey("sebserver.exam.consistency.no-lms-connection");
private final static LocTextKey CONSISTENCY_MESSAGEINVALID_ID_REFERENCE =
new LocTextKey("sebserver.exam.consistency.invalid-lms-id");
private final static LocTextKey CONSISTENCY_MESSAGE_SEB_RESTRICTION_MISMATCH =
new LocTextKey("sebserver.exam.consistencyseb-restriction-mismatch");
private final static LocTextKey AUTO_GEN_CONFIG_ERROR_TITLE =
new LocTextKey("sebserver.exam.autogen.error.config.title");
private final static LocTextKey AUTO_GEN_CONFIG_ERROR_TEXT =
new LocTextKey("sebserver.exam.autogen.error.config.text");
public static final LocTextKey EXAM_FORM_TITLE_KEY = new LocTextKey("sebserver.exam.form.title");
public static final LocTextKey EXAM_FORM_TITLE_IMPORT_KEY = new LocTextKey("sebserver.exam.form.title.import");
private static final LocTextKey FORM_SUPPORTER_TEXT_KEY = new LocTextKey("sebserver.exam.form.supporter");
private static final LocTextKey FORM_QUIT_PWD_TEXT_KEY = new LocTextKey("sebserver.exam.form.quitpwd");
private static final LocTextKey FORM_STATUS_TEXT_KEY = new LocTextKey("sebserver.exam.form.status");
private static final LocTextKey FORM_TYPE_TEXT_KEY = new LocTextKey("sebserver.exam.form.type");
private static final LocTextKey FORM_END_TIME_TEXT_KEY = new LocTextKey("sebserver.exam.form.endtime");
private static final LocTextKey FORM_START_TIME_TEXT_KEY = new LocTextKey("sebserver.exam.form.starttime");
private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY = new LocTextKey("sebserver.exam.form.description");
private static final LocTextKey FORM_NAME_TEXT_KEY = new LocTextKey("sebserver.exam.form.name");
private static final LocTextKey FORM_QUIZ_ID_TEXT_KEY = new LocTextKey("sebserver.exam.form.quizid");
private static final LocTextKey FORM_QUIZ_URL_TEXT_KEY = new LocTextKey("sebserver.exam.form.quizurl");
private static final LocTextKey FORM_LMSSETUP_TEXT_KEY = new LocTextKey("sebserver.exam.form.lmssetup");
private final static LocTextKey ACTION_MESSAGE_SEB_RESTRICTION_RELEASE = new LocTextKey("sebserver.exam.action.sebrestriction.release.confirm");
private static final LocTextKey FORM_EXAM_TEMPLATE_TEXT_KEY = new LocTextKey("sebserver.exam.form.examTemplate");
private static final LocTextKey FORM_EXAM_TEMPLATE_ERROR = new LocTextKey("sebserver.exam.form.examTemplate.error");
private static final LocTextKey EXAM_ARCHIVE_CONFIRM = new LocTextKey("sebserver.exam.action.archive.confirm");
private final static LocTextKey CONSISTENCY_MESSAGE_TITLE = new LocTextKey("sebserver.exam.consistency.title");
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_SUPPORTER = new LocTextKey("sebserver.exam.consistency.missing-supporter");
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_INDICATOR = new LocTextKey("sebserver.exam.consistency.missing-indicator");
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_CONFIG = new LocTextKey("sebserver.exam.consistency.missing-config");
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_SEB_RESTRICTION = new LocTextKey("sebserver.exam.consistency.missing-seb-restriction");
private final static LocTextKey CONSISTENCY_MESSAGE_VALIDATION_LMS_CONNECTION = new LocTextKey("sebserver.exam.consistency.no-lms-connection");
private final static LocTextKey CONSISTENCY_MESSAGEINVALID_ID_REFERENCE = new LocTextKey("sebserver.exam.consistency.invalid-lms-id");
private final static LocTextKey CONSISTENCY_MESSAGE_SEB_RESTRICTION_MISMATCH = new LocTextKey("sebserver.exam.consistencyseb-restriction-mismatch");
private final static LocTextKey AUTO_GEN_CONFIG_ERROR_TITLE = new LocTextKey("sebserver.exam.autogen.error.config.title");
private final static LocTextKey AUTO_GEN_CONFIG_ERROR_TEXT = new LocTextKey("sebserver.exam.autogen.error.config.text");
private final Map<String, LocTextKey> consistencyMessageMapping;
private final PageService pageService;
@ -507,13 +479,20 @@ public class ExamForm implements TemplateComposer {
.withInputSpan(7)
.withEmptyCellSeparation(false))
.addField(FormBuilder.password(
Domain.EXAM.ATTR_QUIT_PASSWORD,
FORM_QUIT_PWD_TEXT_KEY,
exam.quitPassword)
.withInputSpan(3)
.withEmptyCellSeparation(false))
.addField(FormBuilder.multiComboSelection(
Domain.EXAM.ATTR_SUPPORTER,
FORM_SUPPORTER_TEXT_KEY,
StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR),
this.resourceService::examSupporterResources)
.withInputSpan(7)
.withEmptyCellSeparation(false))
.withEmptyCellSpan(4))
.build();
}
@ -615,12 +594,19 @@ public class ExamForm implements TemplateComposer {
this.resourceService::examTypeResources)
.mandatory(true))
.addField(FormBuilder.password(
Domain.EXAM.ATTR_QUIT_PASSWORD,
FORM_QUIT_PWD_TEXT_KEY,
exam.quitPassword))
.addField(FormBuilder.multiComboSelection(
Domain.EXAM.ATTR_SUPPORTER,
FORM_SUPPORTER_TEXT_KEY,
StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR),
this.resourceService::examSupporterResources))
.buildFor(importFromLMS
? this.restService.getRestCall(ImportAsExam.class)
: newExam
@ -643,6 +629,7 @@ public class ExamForm implements TemplateComposer {
null,
null,
ExamStatus.UP_COMING,
null,
false,
null,
true,
@ -679,13 +666,16 @@ public class ExamForm implements TemplateComposer {
.call()
.getOrThrow();
final String quitPassword = examTemplate.getExamAttributes().get(ExamTemplate.ATTR_QUIT_PASSWORD);
form.setFieldValue(Domain.EXAM.ATTR_TYPE, examTemplate.examType.name());
form.setFieldValue(
Domain.EXAM.ATTR_SUPPORTER,
StringUtils.join(examTemplate.supporter, Constants.LIST_SEPARATOR));
form.setFieldValue(Domain.EXAM.ATTR_QUIT_PASSWORD, quitPassword);
} else {
form.setFieldValue(Domain.EXAM.ATTR_TYPE, Exam.ExamType.UNDEFINED.name());
form.setFieldValue(Domain.EXAM.ATTR_SUPPORTER, null);
form.setFieldValue(Domain.EXAM.ATTR_QUIT_PASSWORD, null);
}
} catch (final Exception e) {
context.notifyError(FORM_EXAM_TEMPLATE_ERROR, e);

View file

@ -227,4 +227,5 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
* @param updateId The update identifier given by the update task */
void markLMSAvailability(final String externalQuizId, final boolean available, final String updateId);
void updateQuitPassword(Exam exam, String quitPassword);
}

View file

@ -195,6 +195,13 @@ public class ExamDAOImpl implements ExamDAO {
.onError(error -> log.error("Failed to mark LMS not available: {}", externalQuizId, error));
}
@Override
public void updateQuitPassword(final Exam exam, final String quitPassword) {
this.examRecordDAO
.updateQuitPassword(exam, quitPassword)
.onError(err -> log.error("Failed to update quit password on exam: {}", exam, err));
}
@Override
public Result<Exam> setSEBRestriction(final Long examId, final boolean sebRestriction) {
return this.examRecordDAO
@ -789,6 +796,7 @@ public class ExamDAOImpl implements ExamDAO {
record.getOwner(),
supporter,
status,
record.getQuitPassword(),
BooleanUtils.toBooleanObject(record.getLmsSebRestriction()),
record.getBrowserKeys(),
BooleanUtils.toBooleanObject(record.getActive()),

View file

@ -9,15 +9,13 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl;
import static ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport.*;
import static ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport.quitPassword;
import static org.mybatis.dynamic.sql.SqlBuilder.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
@ -63,13 +61,16 @@ public class ExamRecordDAO {
private final ExamRecordMapper examRecordMapper;
private final ClientConnectionRecordMapper clientConnectionRecordMapper;
private final Cryptor cryptor;
public ExamRecordDAO(
final ExamRecordMapper examRecordMapper,
final ClientConnectionRecordMapper clientConnectionRecordMapper) {
final ClientConnectionRecordMapper clientConnectionRecordMapper,
final Cryptor cryptor) {
this.examRecordMapper = examRecordMapper;
this.clientConnectionRecordMapper = clientConnectionRecordMapper;
this.cryptor = cryptor;
}
@Transactional(readOnly = true)
@ -136,7 +137,7 @@ public class ExamRecordDAO {
.build()
.execute()
.stream()
.map(rec -> rec.getInstitutionId())
.map(ExamRecord::getInstitutionId)
.collect(Collectors.toList());
});
}
@ -234,6 +235,33 @@ public class ExamRecordDAO {
});
}
@Transactional
public Result<ExamRecord> updateQuitPassword(final Exam exam, final String pwd) {
return Result.tryCatch(() -> {
final String examQuitPassword = exam.quitPassword != null
? this.cryptor
.decrypt(exam.quitPassword)
.getOr(exam.quitPassword)
.toString()
: null;
if (Objects.equals(examQuitPassword, pwd)) {
return this.examRecordMapper.selectByPrimaryKey(exam.id);
}
UpdateDSL.updateWithMapper(examRecordMapper::update, examRecord)
.set(quitPassword).equalTo(getEncryptedQuitPassword(pwd))
.where(id, isEqualTo(exam.id))
.build()
.execute();
return this.examRecordMapper.selectByPrimaryKey(exam.id);
})
.onError(TransactionHandler::rollback);
}
@Transactional
public Result<ExamRecord> updateState(final Long examId, final ExamStatus status, final String updateId) {
return recordById(examId)
@ -270,41 +298,39 @@ public class ExamRecordDAO {
}
if (exam.status != null && !exam.status.name().equals(oldRecord.getStatus())) {
log.info("Exam state change on save. Exam. {}, Old state: {}, new state: {}",
exam.externalId,
oldRecord.getStatus(),
exam.status);
log.info(
"Exam state change on save. Exam. {}, Old state: {}, new state: {}",
exam.externalId,
oldRecord.getStatus(),
exam.status);
}
final ExamRecord examRecord = new ExamRecord(
exam.id,
null, null, null, null,
(exam.supporter != null)
? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
: null,
(exam.type != null)
? exam.type.name()
: null,
null,
exam.browserExamKeys,
null,
1, // seb restriction (deprecated)
null, // updating
null, // lastUpdate
null, // active
exam.examTemplateId,
Utils.getMillisecondsNow(),
exam.lmsSetupId == null ? exam.name : null,
exam.lmsSetupId == null ? exam.startTime : null,
exam.lmsSetupId == null ? exam.endTime : null,
null);
UpdateDSL.updateWithMapper(examRecordMapper::update, examRecord)
.set(supporter).equalTo((exam.supporter != null)
? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
: null)
.set(type).equalTo((exam.type != null)
? exam.type.name()
: ExamType.UNDEFINED.name())
.set(quitPassword).equalTo(getEncryptedQuitPassword(exam.quitPassword))
.set(browserKeys).equalToWhenPresent(exam.browserExamKeys)
.set(lmsSebRestriction).equalTo(1) // seb restriction (deprecated)
.set(examTemplateId).equalTo(exam.examTemplateId)
.set(lastModified).equalTo(Utils.getMillisecondsNow())
.set(quizName).equalToWhenPresent(exam.lmsSetupId == null ? exam.name : null)
.set(quizStartTime).equalToWhenPresent(exam.lmsSetupId == null ? exam.startTime : null)
.set(quizEndTime).equalToWhenPresent(exam.lmsSetupId == null ? exam.endTime : null)
.where(id, isEqualTo(exam.id))
.build()
.execute();
this.examRecordMapper.updateByPrimaryKeySelective(examRecord);
return this.examRecordMapper.selectByPrimaryKey(exam.id);
})
.onError(TransactionHandler::rollback);
}
@Transactional
public Result<ExamRecord> updateFromQuizData(
final Long examId,
@ -445,7 +471,7 @@ public class ExamRecordDAO {
? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
: null,
(exam.type != null) ? exam.type.name() : ExamType.UNDEFINED.name(),
null, // quitPassword
getEncryptedQuitPassword(exam.quitPassword),
null, // browser keys
(exam.status != null) ? exam.status.name() : ExamStatus.UP_COMING.name(),
1, // seb restriction (deprecated)
@ -554,4 +580,14 @@ public class ExamRecordDAO {
});
}
private String getEncryptedQuitPassword(final String pwd) {
return (StringUtils.isNotBlank(pwd))
? this.cryptor
.encryptCheckAlreadyEncrypted(pwd)
.onError(err -> log.error("failed to encrypt quit password, skip...", err))
.getOr(pwd)
.toString()
: null;
}
}

View file

@ -210,6 +210,15 @@ public class ExamTemplateDAOImpl implements ExamTemplateDAO {
indicatorsJSON,
BooleanUtils.toInteger(data.institutionalDefault));
final String quitPassword = data.getExamAttributes().get(ExamTemplate.ATTR_QUIT_PASSWORD);
if (StringUtils.isNotBlank(quitPassword)) {
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM_TEMPLATE,
data.id,
ExamTemplate.ATTR_QUIT_PASSWORD,
quitPassword);
}
this.examTemplateRecordMapper.insert(newRecord);
return newRecord;
})

View file

@ -163,6 +163,8 @@ public interface ExamAdminService {
* @param exam the exam that has been changed and saved */
void notifyExamSaved(Exam exam);
void applyQuitPassword(Exam entity);
static void newExamFieldValidation(final POSTMapper postParams) {
noLMSFieldValidation(new Exam(postParams));
}
@ -337,5 +339,4 @@ public interface ExamAdminService {
}
}
}

View file

@ -8,11 +8,13 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.exam;
import ch.ethz.seb.sebserver.gbl.util.Result;
public interface ExamConfigurationValueService {
public static final String CONFIG_ATTR_NAME_QUIT_LINK = "quitURL";
public static final String CONFIG_ATTR_NAME_QUIT_SECRET = "hashedQuitPassword";
public static final String CONFIG_ATTR_NAME_ALLOWED_SEB_VERSION = "sebAllowedVersions";
String CONFIG_ATTR_NAME_QUIT_LINK = "quitURL";
String CONFIG_ATTR_NAME_QUIT_SECRET = "hashedQuitPassword";
String CONFIG_ATTR_NAME_ALLOWED_SEB_VERSION = "sebAllowedVersions";
/** Get the actual SEB settings attribute value for the exam configuration mapped as default configuration
* to the given exam
@ -29,7 +31,7 @@ public interface ExamConfigurationValueService {
*
* @param examId The exam identifier
* @param configAttributeName The name of the SEB settings attribute
* @param The default value that is given back if there is no value from configuration
* @param defaultValue default value that is given back if there is no value from configuration
* @return The current value of the above SEB settings attribute and given exam. */
String getMappedDefaultConfigAttributeValue(
Long examId,
@ -39,8 +41,18 @@ public interface ExamConfigurationValueService {
/** Get the quitPassword SEB Setting from the Exam Configuration that is applied to the given exam.
*
* @param examId Exam identifier
* @return the vlaue of the quitPassword SEB Setting */
String getQuitSecret(Long examId);
* @return the value of the quitPassword SEB Setting */
String getQuitPassword(Long examId);
String getQuitPasswordFromConfigTemplate(Long configTemplateId);
/** Used to apply the quit pass given from the exam to all exam configuration for the exam.
*
* @param examId The exam identifier
* @param quitPassword The quit password to set to all exam configuration of the given exam
* @return Result to the given exam id or to an error when happened
*/
Result<Long> applyQuitPasswordToConfigs(Long examId, String quitPassword);
/** Get the quitLink SEB Setting from the Exam Configuration that is applied to the given exam.
*

View file

@ -328,6 +328,13 @@ public class ExamAdminServiceImpl implements ExamAdminService {
this.proctoringAdminService.notifyExamSaved(exam);
}
@Override
public void applyQuitPassword(final Exam exam) {
this.examConfigurationValueService
.applyQuitPasswordToConfigs(exam.id, exam.quitPassword)
.getOrThrow();
}
private Result<Exam> initAdditionalAttributesForMoodleExams(final Exam exam) {
return Result.tryCatch(() -> {

View file

@ -8,6 +8,11 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
import java.util.Objects;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.gbl.util.Result;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -59,24 +64,16 @@ public class ExamConfigurationValueServiceImpl implements ExamConfigurationValue
final Long configId = this.examConfigurationMapDAO
.getDefaultConfigurationNode(examId)
.flatMap(nodeId -> this.configurationDAO.getConfigurationLastStableVersion(nodeId))
.flatMap(this.configurationDAO::getConfigurationLastStableVersion)
.map(config -> config.id)
.onError(error -> log.warn("Failed to get default Exam Config for exam: {} cause: {}",
examId, error.getMessage()))
.getOr(null);
if (configId == null) {
return defaultValue;
}
final Long attrId = this.configurationAttributeDAO
.getAttributeIdByName(configAttributeName)
.onError(error -> log.error("Failed to get attribute id with name: {} for exam: {}",
configAttributeName, examId, error))
.getOr(null);
return this.configurationValueDAO
.getConfigAttributeValue(configId, attrId)
.getConfigAttributeValue(configId, getAttributeId(configAttributeName))
.onError(error -> log.warn(
"Failed to get exam config attribute: {} {} error: {}",
examId,
@ -92,8 +89,10 @@ public class ExamConfigurationValueServiceImpl implements ExamConfigurationValue
}
}
@Override
public String getQuitSecret(final Long examId) {
public String getQuitPassword(final Long examId) {
try {
final String quitSecretEncrypted = getMappedDefaultConfigAttributeValue(
@ -119,6 +118,86 @@ public class ExamConfigurationValueServiceImpl implements ExamConfigurationValue
return StringUtils.EMPTY;
}
@Override
public String getQuitPasswordFromConfigTemplate(final Long configTemplateId) {
try {
final Long configId = this.configurationDAO
.getFollowupConfigurationId(configTemplateId)
.getOrThrow();
return this.configurationValueDAO
.getConfigAttributeValue(configId, getAttributeId(CONFIG_ATTR_NAME_QUIT_SECRET))
.getOrThrow();
} catch (final Exception e) {
log.error("Failed to get quit password from configuration template", e);
return null;
}
}
@Override
public Result<Long> applyQuitPasswordToConfigs(final Long examId, final String quitSecret) {
return Result.tryCatch(() -> {
final String oldQuitPassword = this.getQuitPassword(examId);
final String newQuitPassword = quitSecret != null
? this.cryptor
.decrypt(quitSecret)
.getOr(quitSecret)
.toString()
: null;
if (Objects.equals(oldQuitPassword, newQuitPassword)) {
return examId;
}
final Long configNodeId = this.examConfigurationMapDAO
.getDefaultConfigurationNode(examId)
.getOr(null);
if (configNodeId == null) {
log.info("No Exam Configuration found for exam {} to apply quitPassword", examId);
return examId;
}
final Long attrId = getAttributeId(CONFIG_ATTR_NAME_QUIT_SECRET);
if (attrId == null) {
return examId;
}
final Configuration followupConfig = this.configurationDAO.getFollowupConfiguration(configNodeId)
.onError(error -> log.warn("Failed to get followup config for {} cause {}",
configNodeId,
error.getMessage()))
.getOr(null);
final ConfigurationValue configurationValue = new ConfigurationValue(
null,
followupConfig.institutionId,
followupConfig.id,
attrId,
0,
quitSecret
);
this.configurationValueDAO
.save(configurationValue)
.onError(err -> log.error(
"Failed to save quit password to config value: {}",
configurationValue,
err));
// TODO possible without save to history?
this.configurationDAO
.saveToHistory(configNodeId)
.onError(error -> log.warn("Failed to save to history for exam: {} cause: {}",
examId, error.getMessage()));
return examId;
});
}
@Override
public String getQuitLink(final Long examId) {
try {
@ -149,4 +228,12 @@ public class ExamConfigurationValueServiceImpl implements ExamConfigurationValue
}
}
private Long getAttributeId(final String configAttributeName) {
return this.configurationAttributeDAO
.getAttributeIdByName(configAttributeName)
.onError(error -> log.error("Failed to get attribute id with name: {}",
configAttributeName, error))
.getOr(null);
}
}

View file

@ -228,7 +228,6 @@ public class ExamTemplateServiceImpl implements ExamTemplateService {
.getOrThrow(error -> new APIMessageException(
ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CONFIG_LINKING,
error));
}
} else {
if (log.isDebugEnabled()) {

View file

@ -165,7 +165,6 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
}
@Override
@Transactional
public Result<Exam> saveSEBRestrictionToExam(final Exam exam, final SEBRestriction sebRestriction) {
if (log.isDebugEnabled()) {
@ -181,6 +180,7 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
exam.supporter,
exam.status,
null,
null,
(browserExamKeys != null && !browserExamKeys.isEmpty())
? StringUtils.join(browserExamKeys, Constants.LIST_SEPARATOR_CHAR)
: StringUtils.EMPTY,

View file

@ -136,7 +136,7 @@ public class MoodlePluginCourseRestriction implements SEBRestrictionAPI {
final ArrayList<String> beks = new ArrayList<>(sebRestrictionData.browserExamKeys);
final ArrayList<String> configKeys = new ArrayList<>(sebRestrictionData.configKeys);
final String quitLink = this.examConfigurationValueService.getQuitLink(exam.id);
final String quitSecret = this.examConfigurationValueService.getQuitSecret(exam.id);
final String quitSecret = this.examConfigurationValueService.getQuitPassword(exam.id);
final String additionalBEK = exam.getAdditionalAttribute(
SEBRestrictionService.ADDITIONAL_ATTR_ALTERNATIVE_SEB_BEK);

View file

@ -368,7 +368,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
post.configKeys = new ArrayList<>(restriction.configKeys);
if (this.restrictWithAdditionalAttributes) {
post.quitLink = this.examConfigurationValueService.getQuitLink(restriction.examId);
post.quitSecret = this.examConfigurationValueService.getQuitSecret(restriction.examId);
post.quitSecret = this.examConfigurationValueService.getQuitPassword(restriction.examId);
}
final RestrictionData r =
this.apiPost(restTemplate, url, post, RestrictionDataPost.class, RestrictionData.class);

View file

@ -18,32 +18,32 @@ public interface ExamConfigUpdateService {
/** Used to process a SEB Exam Configuration change that can also effect some
* running exams that as the specified configuration attached.
*
* <p>
* This deals with the whole functionality the underling data-structure provides. So
* it assumes a N to M relationship between a SEB Exam Configuration and an Exam even
* if this may currently not be possible in case of implemented restrictions.
*
* <p>
* First of all a consistency check is applied that checks if there is no running Exam
* involved that has currently active SEB Client connection. Active SEB Client connections are
* established connections that are not yet closed and connection attempts that are older the a
* defined time interval.
*
* <p>
* After this check passed, the system places an update-lock on each Exam that is involved on the
* data-base level and commit this immediately so that this can prevent new SEB Client connection
* database level and commit this immediately so that this can prevent new SEB Client connection
* attempts to be allowed.
*
* <p>
* After placing update-locks the fist check is done again to ensure there where no new SEB Client
* connection attempts in the meantime. If there where, the procedure will stop and rollback all
* changes so far.
*
* <p>
* If everything is locked the changes to the SEB Exam Configuration will be saved to the data-base
* and all involved caches will be flushed to ensure the changed will take effect on next request.
*
* <p>
* The last step is to update the SEB restriction on the LMS for every involved exam if it is running
* and the feature is switched on. If something goes wrong during the update for an exam here, no
* rollback of the entire procedure is applied. Instead the error is logged and the update can be
* later triggered manually by an administrator.
*
* <p>
* If there is any other error during the procedure the changes are rolled back and a force release of
* the update-locks is applied to ensure all involved Exams are not locked anymore.
*

View file

@ -33,9 +33,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCac
/** A Service to handle running exam sessions */
public interface ExamSessionService {
public static final Predicate<ClientConnection> ACTIVE_CONNECTION_FILTER =
Predicate<ClientConnection> ACTIVE_CONNECTION_FILTER =
cc -> cc.status == ConnectionStatus.ACTIVE;
public static final Predicate<ClientConnectionData> ACTIVE_CONNECTION_DATA_FILTER =
Predicate<ClientConnectionData> ACTIVE_CONNECTION_DATA_FILTER =
ccd -> ccd.clientConnection.status == ConnectionStatus.ACTIVE;
/** Get the underling ExamDAO service.

View file

@ -13,6 +13,8 @@ import java.util.Collections;
import java.util.function.Function;
import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
@ -46,6 +48,8 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
private final ExamSessionService examSessionService;
private final ExamUpdateHandler examUpdateHandler;
private final ExamAdminService examAdminService;
private final ExamConfigurationValueService examConfigurationValueService;
protected ExamConfigUpdateServiceImpl(
final ExamDAO examDAO,
@ -53,7 +57,8 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
final ExamConfigurationMapDAO examConfigurationMapDAO,
final ExamSessionService examSessionService,
final ExamUpdateHandler examUpdateHandler,
final ExamAdminService examAdminService) {
final ExamAdminService examAdminService,
final ExamConfigurationValueService examConfigurationValueService) {
this.examDAO = examDAO;
this.configurationDAO = configurationDAO;
@ -61,13 +66,15 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
this.examSessionService = examSessionService;
this.examUpdateHandler = examUpdateHandler;
this.examAdminService = examAdminService;
this.examConfigurationValueService = examConfigurationValueService;
}
// processing:
// check running exam integrity (No running exam with active SEB client-connection available)
// if OK, create an update-id and for each exam, create an update-lock on DB (This also prevents new SEB client connection attempts during update)
// check running exam integrity again after lock to ensure there where no SEB Client connection attempts in the meantime
// check running exam integrity again after lock to ensure there were no SEB Client connection attempts in the meantime
// store the new configuration values (into history) so that they take effect
// check if quit password has changed and if so set it too for to (SEBSERV-482)
// generate the new Config Key and update the Config Key within the LMSSetup API for each exam (delete old Key and add new Key)
// evict each Exam from cache and release the update-lock on DB
@Override
@ -129,6 +136,10 @@ 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)
for (final Exam exam : exams) {
// check if quit password has changed and if so set it for the exam (SEBSERV-482)
examDAO.updateQuitPassword(exam, examConfigurationValueService.getQuitPassword(exam.id));
if (exam.getStatus() == ExamStatus.RUNNING && exam.lmsSetupId != null) {
this.examUpdateHandler
@ -202,6 +213,15 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
mapping.configurationNodeId))
.getOrThrow();
// update quit password if needed (SEBSERV-482)
if (StringUtils.isBlank(exam.quitPassword)) {
// copy quit password from config to exam
examDAO.updateQuitPassword(exam, examConfigurationValueService.getQuitPassword(exam.id));
} else {
// copy quit password from exam to config
examConfigurationValueService.applyQuitPasswordToConfigs(exam.id, exam.quitPassword);
}
// update seb client restriction if the feature is activated for the exam
this.examUpdateHandler
.getSEBRestrictionService()

View file

@ -339,7 +339,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
return;
}
// for distributed setups check if cached config is still up to date. Flush and reload if not.
// for distributed setups check if cached config is still up-to-date. Flush and reload if not.
if (this.distributedSetup && !this.examSessionCacheService.isUpToDate(sebConfigForExam)) {
if (log.isDebugEnabled()) {
@ -530,9 +530,8 @@ public class ExamSessionServiceImpl implements ExamSessionService {
@Override
public Result<Exam> updateExamCache(final Long examId) {
// TODO check how often this is called in distributed environments
//System.out.println("************** performance check: updateExamCache");
// TODO make interval access. this should only check when the last check was more then 5 seconds ago
// TODO is this really needed?
try {
final Cache cache = this.cacheManager.getCache(ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM);
final ValueWrapper valueWrapper = cache.get(examId);
@ -554,7 +553,6 @@ public class ExamSessionServiceImpl implements ExamSessionService {
.getOr(false);
if (!BooleanUtils.toBoolean(isUpToDate)) {
// TODO this should only flush the exam cache but not the SEB connection cache
return flushCache(exam);
} else {
return Result.of(exam);
@ -603,7 +601,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
.collect(Collectors.toSet());
this.clientConnectionDAO.getClientConnectionsOutOfSyc(examId, timestamps)
.getOrElse(() -> Collections.emptySet())
.getOrElse(Collections::emptySet)
.stream()
.forEach(this.examSessionCacheService::evictClientConnection);

View file

@ -19,6 +19,7 @@ import java.util.stream.Collectors;
import javax.validation.Valid;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
@ -652,6 +653,8 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
return entity;
});
this.examAdminService.applyQuitPassword(entity);
if (!errors.isEmpty()) {
errors.add(0, ErrorMessage.EXAM_IMPORT_ERROR_AUTO_SETUP.of(
entity.getModelId(),
@ -669,6 +672,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
protected Result<Exam> notifySaved(final Exam entity) {
return Result.tryCatch(() -> {
this.examAdminService.notifyExamSaved(entity);
this.examAdminService.applyQuitPassword(entity);
this.examSessionService.flushCache(entity);
return entity;
});
@ -684,7 +688,8 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
protected Result<Exam> validForSave(final Exam entity) {
return super.validForSave(entity)
.map(this::checkExamSupporterRole)
.map(ExamAdminService::noLMSFieldValidation);
.map(ExamAdminService::noLMSFieldValidation)
.map(this::checkQuitPasswordChange);
}
@Override
@ -702,6 +707,22 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
return checkNoActiveSEBClientConnections(entity);
}
private Exam checkQuitPasswordChange(final Exam exam) {
if (this.examSessionService.isExamRunning(exam.id) &&
examSessionService.hasActiveSEBClientConnections(exam.id)) {
final Exam oldExam = this.examDAO.byPK(exam.id).getOrThrow();
if (!oldExam.quitPassword.equals(exam.quitPassword)) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
EXAM.ATTR_QUIT_PASSWORD,
EXAM.ATTR_QUIT_PASSWORD,
"exam:quitPassword:changeDenied:")));
}
}
return exam;
}
private Exam checkExamSupporterRole(final Exam exam) {
final Set<String> examSupporter = this.userDAO.all(
this.authorization.getUserService().getCurrentUser().getUserInfo().institutionId,

View file

@ -8,18 +8,17 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
@ -60,8 +59,11 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_TEMPLATE_ENDPOINT)
public class ExamTemplateController extends EntityController<ExamTemplate, ExamTemplate> {
private static final Logger log = LoggerFactory.getLogger(ExamTemplateController.class);
private final ExamTemplateDAO examTemplateDAO;
private final ProctoringAdminService proctoringServiceSettingsService;
private final ExamConfigurationValueService examConfigurationValueService;
protected ExamTemplateController(
final AuthorizationService authorization,
@ -70,7 +72,8 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
final UserActivityLogDAO userActivityLogDAO,
final PaginationService paginationService,
final BeanValidationService beanValidationService,
final ProctoringAdminService proctoringServiceSettingsService) {
final ProctoringAdminService proctoringServiceSettingsService,
final ExamConfigurationValueService examConfigurationValueService) {
super(
authorization,
@ -82,6 +85,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
this.examTemplateDAO = entityDAO;
this.proctoringServiceSettingsService = proctoringServiceSettingsService;
this.examConfigurationValueService = examConfigurationValueService;
}
@RequestMapping(
@ -101,6 +105,45 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
.getOrThrow();
}
@Override
protected Result<ExamTemplate> validForCreate(final ExamTemplate entity) {
return super.validForCreate(entity)
.map(this::applyQuitPasswordIfNeeded);
}
@Override
protected Result<ExamTemplate> validForSave(final ExamTemplate entity) {
return super.validForSave(entity)
.map(this::applyQuitPasswordIfNeeded);
}
private ExamTemplate applyQuitPasswordIfNeeded(final ExamTemplate entity) {
if (entity.configTemplateId != null) {
try {
final String quitPassword = this.examConfigurationValueService
.getQuitPasswordFromConfigTemplate(entity.configTemplateId);
final HashMap<String, String> attributes = new HashMap<>(entity.examAttributes);
attributes.put(ExamTemplate.ATTR_QUIT_PASSWORD, quitPassword);
return new ExamTemplate(
entity.id,
entity.institutionId,
entity.name,
entity.description,
entity.examType,
entity.supporter,
entity.configTemplateId,
entity.institutionalDefault,
entity.indicatorTemplates,
entity.clientGroupTemplates,
attributes
);
} catch (final Exception e) {
log.error("Failed to apply quit password to Exam Template.", e);
}
}
return entity;
}
// ****************************************************************************
// **** Indicator Templates
@ -466,7 +509,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
final String sortBy = PageSortOrder.decode(sort);
return indicators -> {
final List<IndicatorTemplate> list = indicators.stream().collect(Collectors.toList());
final List<IndicatorTemplate> list = new ArrayList<>(indicators);
if (StringUtils.isBlank(sort)) {
return list;
}
@ -487,7 +530,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
final String sortBy = PageSortOrder.decode(sort);
return clientGroups -> {
final List<ClientGroupTemplate> list = clientGroups.stream().collect(Collectors.toList());
final List<ClientGroupTemplate> list = new ArrayList<>(clientGroups);
if (StringUtils.isBlank(sort)) {
return list;
}

View file

@ -1,11 +1,11 @@
server.address=localhost
server.port=8080
server.port=8090
sebserver.gui.http.external.scheme=http
sebserver.gui.entrypoint=/gui
sebserver.gui.webservice.protocol=http
sebserver.gui.webservice.address=localhost
sebserver.gui.webservice.port=8080
sebserver.gui.webservice.port=8090
sebserver.gui.webservice.apipath=/admin-api/v1
# defines the polling interval that is used to poll the webservice for client connection data on a monitored exam page
sebserver.gui.webservice.poll-interval=1000

View file

@ -0,0 +1,4 @@
-- ----------------------------------------------------------------
-- Add SEB Settings GUI additions (SEBSERV-414 and SEBSERV-465)
-- ----------------------------------------------------------------

View file

@ -594,6 +594,8 @@ sebserver.exam.form.supporter.tooltip=A list of users that are allowed to suppor
sebserver.exam.form.examTemplate=Exam Template
sebserver.exam.form.examTemplate.tooltip=Select an exam template to automatically create the exam, exam configuration and indicators defined by the template.
sebserver.exam.form.examTemplate.error=Failed to set type and supporter form template.<br/>Please make sure you have selected the right type and supporter or tray again.
sebserver.exam.form.quitpwd=Quit Password
sebserver.exam.form.quitpwd.tooltip=A password if set a SEB user must provide to be able to quit SEB<br/>This mirrors the Quit Password from SEB Settings.
sebserver.exam.form.export.config.popup.title=Export Connection Configuration for Starting the Exam
sebserver.exam.form.export.config.name=Name

View file

@ -205,7 +205,7 @@ public class ModelObjectJSONGenerator {
1L, 1L, 1L, "externalId", true, "name", DateTime.now(), DateTime.now(),
ExamType.BYOD, "owner",
Arrays.asList("user1", "user2"),
ExamStatus.RUNNING, false, "browserExamKeys", true, null, null, null, null);
ExamStatus.RUNNING, null, false, "browserExamKeys", true, null, null, null, null);
System.out.println(domainObject.getClass().getSimpleName() + ":");
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));

View file

@ -904,6 +904,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
null,
Utils.immutableCollectionOf(userId),
null,
null,
false,
null,
true,

View file

@ -62,6 +62,7 @@ public class ExamAPITest extends AdministrationAPIIntegrationTester {
exam.owner,
Arrays.asList("user5"),
null,
null,
false,
null,
true,
@ -91,6 +92,7 @@ public class ExamAPITest extends AdministrationAPIIntegrationTester {
exam.owner,
Arrays.asList("user2"),
null,
null,
false,
null,
true,

View file

@ -91,6 +91,7 @@ public class OlatLmsAPITemplateTest extends AdministrationAPIIntegrationTester {
null,
null,
ExamStatus.FINISHED,
null,
Boolean.FALSE,
null,
Boolean.FALSE,

View file

@ -121,7 +121,8 @@ public class SEBClientEventCSVExporterTest {
final ClientEventRecord event = new ClientEventRecord(0L, 1L, 2, 3L, 4L, new BigDecimal(5), "text");
final Exam exam = new Exam(0L, 1L, 3L, "externalid", true, "name", new DateTime(1L),
new DateTime(1L),
Exam.ExamType.BYOD, "owner", new ArrayList<>(), Exam.ExamStatus.RUNNING, false, "bek", true,
Exam.ExamType.BYOD, "owner", new ArrayList<>(), Exam.ExamStatus.RUNNING,
null, false, "bek", true,
"lastUpdate", 4L, null, attrs);
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
final BufferedOutputStream output = new BufferedOutputStream(stream);
@ -150,7 +151,8 @@ public class SEBClientEventCSVExporterTest {
final ClientEventRecord event = new ClientEventRecord(0L, 1L, 2, 3L, 4L, new BigDecimal(5), "text");
final Exam exam = new Exam(0L, 1L, 3L, "externalid", true, "name", new DateTime(1L),
new DateTime(1L),
Exam.ExamType.BYOD, "owner", new ArrayList<>(), Exam.ExamStatus.RUNNING, false, "bek", true,
Exam.ExamType.BYOD, "owner", new ArrayList<>(), Exam.ExamStatus.RUNNING,
null, false, "bek", true,
"lastUpdate", 4L, null, attrs);
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
final BufferedOutputStream output = new BufferedOutputStream(stream);

View file

@ -52,7 +52,8 @@ public class MoodlePluginCourseRestrictionTest {
public void getNoneExistingRestriction() {
final MoodlePluginCourseRestriction candidate = crateMockup();
final Exam exam = new Exam(1L, 1L, 1L, "101:1:c1:i1",
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null);
final Result<SEBRestriction> sebClientRestriction = candidate.getSEBClientRestriction(exam);
@ -67,7 +68,8 @@ public class MoodlePluginCourseRestrictionTest {
public void getSetGetRestriction() {
final MoodlePluginCourseRestriction candidate = crateMockup();
final Exam exam = new Exam(1L, 1L, 1L, "101:1:c1:i1",
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null);
final SEBRestriction restriction = new SEBRestriction(
exam.id,
@ -156,7 +158,7 @@ public class MoodlePluginCourseRestrictionTest {
final ExamConfigurationValueService examConfigurationValueService =
Mockito.mock(ExamConfigurationValueService.class);
Mockito.when(examConfigurationValueService.getQuitLink(Mockito.anyLong())).thenReturn("quitLink");
Mockito.when(examConfigurationValueService.getQuitSecret(Mockito.anyLong())).thenReturn("quitSecret");
Mockito.when(examConfigurationValueService.getQuitPassword(Mockito.anyLong())).thenReturn("quitSecret");
return new MoodlePluginCourseRestriction(jsonMapper, moodleMockupRestTemplateFactory,
examConfigurationValueService);