SEBSERV-482 implementation no testing yet
This commit is contained in:
parent
74d4b1ff7d
commit
ac21400388
32 changed files with 428 additions and 171 deletions
|
@ -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
|
/** 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)
|
* 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,
|
* SEB-Server uses Spring Boot as main framework is divided into two main components,
|
||||||
* a webservice component that implements the business logic, persistence management
|
* 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
|
* 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.
|
* to https.
|
||||||
*
|
*
|
||||||
* NOTE: This works with TomcatServletWebServerFactory and embedded tomcat.
|
* NOTE: This works with TomcatServletWebServerFactory and embedded tomcat.
|
||||||
|
|
|
@ -39,25 +39,10 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
public final class Exam implements GrantEntity {
|
public final class Exam implements GrantEntity {
|
||||||
|
|
||||||
public static final Exam EMPTY_EXAM = new Exam(
|
public static final Exam EMPTY_EXAM = new Exam(
|
||||||
-1L,
|
-1L, -1L, -1L, Constants.EMPTY_NOTE, false, Constants.EMPTY_NOTE,
|
||||||
-1L,
|
null, null, ExamType.UNDEFINED, null, null, ExamStatus.FINISHED,
|
||||||
-1L,
|
null, Boolean.FALSE, null, Boolean.FALSE, null,
|
||||||
Constants.EMPTY_NOTE,
|
null, null, null);
|
||||||
false,
|
|
||||||
Constants.EMPTY_NOTE,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
ExamType.UNDEFINED,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
ExamStatus.FINISHED,
|
|
||||||
Boolean.FALSE,
|
|
||||||
null,
|
|
||||||
Boolean.FALSE,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
|
|
||||||
public static final String FILTER_ATTR_TYPE = "type";
|
public static final String FILTER_ATTR_TYPE = "type";
|
||||||
public static final String FILTER_ATTR_STATUS = "status";
|
public static final String FILTER_ATTR_STATUS = "status";
|
||||||
|
@ -130,6 +115,9 @@ public final class Exam implements GrantEntity {
|
||||||
@JsonProperty(EXAM.ATTR_STATUS)
|
@JsonProperty(EXAM.ATTR_STATUS)
|
||||||
public final ExamStatus status;
|
public final ExamStatus status;
|
||||||
|
|
||||||
|
@JsonProperty(EXAM.ATTR_QUIT_PASSWORD)
|
||||||
|
public final String quitPassword;
|
||||||
|
|
||||||
@JsonProperty(EXAM.ATTR_LMS_SEB_RESTRICTION)
|
@JsonProperty(EXAM.ATTR_LMS_SEB_RESTRICTION)
|
||||||
public final Boolean sebRestriction;
|
public final Boolean sebRestriction;
|
||||||
|
|
||||||
|
@ -170,6 +158,7 @@ public final class Exam implements GrantEntity {
|
||||||
@JsonProperty(EXAM.ATTR_OWNER) final String owner,
|
@JsonProperty(EXAM.ATTR_OWNER) final String owner,
|
||||||
@JsonProperty(EXAM.ATTR_SUPPORTER) final Collection<String> supporter,
|
@JsonProperty(EXAM.ATTR_SUPPORTER) final Collection<String> supporter,
|
||||||
@JsonProperty(EXAM.ATTR_STATUS) final ExamStatus status,
|
@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_LMS_SEB_RESTRICTION) final Boolean sebRestriction,
|
||||||
@JsonProperty(EXAM.ATTR_BROWSER_KEYS) final String browserExamKeys,
|
@JsonProperty(EXAM.ATTR_BROWSER_KEYS) final String browserExamKeys,
|
||||||
@JsonProperty(EXAM.ATTR_ACTIVE) final Boolean active,
|
@JsonProperty(EXAM.ATTR_ACTIVE) final Boolean active,
|
||||||
|
@ -189,6 +178,7 @@ public final class Exam implements GrantEntity {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.status = (status != null) ? status : getStatusFromDate(startTime, endTime);
|
this.status = (status != null) ? status : getStatusFromDate(startTime, endTime);
|
||||||
|
this.quitPassword = quitPassword;
|
||||||
this.sebRestriction = sebRestriction;
|
this.sebRestriction = sebRestriction;
|
||||||
this.browserExamKeys = browserExamKeys;
|
this.browserExamKeys = browserExamKeys;
|
||||||
this.active = (active != null) ? active : Boolean.TRUE;
|
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.type = postMap.getEnum(EXAM.ATTR_TYPE, ExamType.class, ExamType.UNDEFINED);
|
||||||
this.owner = postMap.getString(EXAM.ATTR_OWNER);
|
this.owner = postMap.getString(EXAM.ATTR_OWNER);
|
||||||
this.status = postMap.getEnum(EXAM.ATTR_STATUS, ExamStatus.class, getStatusFromDate(this.startTime, this.endTime));
|
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.sebRestriction = null;
|
||||||
this.browserExamKeys = null;
|
this.browserExamKeys = null;
|
||||||
this.active = postMap.getBoolean(EXAM.ATTR_ACTIVE);
|
this.active = postMap.getBoolean(EXAM.ATTR_ACTIVE);
|
||||||
|
@ -262,6 +253,7 @@ public final class Exam implements GrantEntity {
|
||||||
EXAM.ATTR_STATUS,
|
EXAM.ATTR_STATUS,
|
||||||
ExamStatus.class,
|
ExamStatus.class,
|
||||||
getStatusFromDate(this.startTime, this.endTime));
|
getStatusFromDate(this.startTime, this.endTime));
|
||||||
|
this.quitPassword = mapper.getString(EXAM.ATTR_QUIT_PASSWORD);
|
||||||
this.sebRestriction = null;
|
this.sebRestriction = null;
|
||||||
this.browserExamKeys = mapper.getString(EXAM.ATTR_BROWSER_KEYS);
|
this.browserExamKeys = mapper.getString(EXAM.ATTR_BROWSER_KEYS);
|
||||||
this.active = mapper.getBoolean(EXAM.ATTR_ACTIVE);
|
this.active = mapper.getBoolean(EXAM.ATTR_ACTIVE);
|
||||||
|
@ -389,6 +381,10 @@ public final class Exam implements GrantEntity {
|
||||||
return this.status;
|
return this.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getQuitPassword() {
|
||||||
|
return quitPassword;
|
||||||
|
}
|
||||||
|
|
||||||
public String getBrowserExamKeys() {
|
public String getBrowserExamKeys() {
|
||||||
return this.browserExamKeys;
|
return this.browserExamKeys;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 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_CLIENT_GROUP_TEMPLATES = "CLIENT_GROUP_TEMPLATES";
|
||||||
public static final String ATTR_EXAM_ATTRIBUTES = "EXAM_ATTRIBUTES";
|
public static final String ATTR_EXAM_ATTRIBUTES = "EXAM_ATTRIBUTES";
|
||||||
|
public static final String ATTR_QUIT_PASSWORD = "quitPassword";
|
||||||
|
|
||||||
@JsonProperty(EXAM_TEMPLATE.ATTR_ID)
|
@JsonProperty(EXAM_TEMPLATE.ATTR_ID)
|
||||||
public final Long id;
|
public final Long id;
|
||||||
|
@ -243,8 +244,7 @@ public class ExamTemplate implements GrantEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ExamTemplate createNew(final Long institutionId) {
|
public static ExamTemplate createNew(final Long institutionId) {
|
||||||
return new ExamTemplate(null, institutionId, null, null, ExamType.UNDEFINED, null, null, false, null, null,
|
return new ExamTemplate(null, institutionId, null, null, ExamType.UNDEFINED, null, null, false, null, null, null);
|
||||||
null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import javax.crypto.spec.GCMParameterSpec;
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
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;
|
||||||
import org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.BCPKCS12KeyStore;
|
import org.bouncycastle.jcajce.provider.keystore.pkcs12.PKCS12KeyStoreSpi.BCPKCS12KeyStore;
|
||||||
import org.slf4j.Logger;
|
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) {
|
public static Result<CharSequence> decrypt(final CharSequence cipher, final CharSequence secret) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
if (cipher == null) {
|
if (cipher == null) {
|
||||||
|
@ -183,4 +197,5 @@ public class Cryptor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,61 +83,33 @@ public class ExamForm implements TemplateComposer {
|
||||||
protected static final String ATTR_EDITABLE = "ATTR_EDITABLE";
|
protected static final String ATTR_EDITABLE = "ATTR_EDITABLE";
|
||||||
protected static final String ATTR_EXAM_STATUS = "ATTR_EXAM_STATUS";
|
protected static final String ATTR_EXAM_STATUS = "ATTR_EXAM_STATUS";
|
||||||
|
|
||||||
public static final LocTextKey EXAM_FORM_TITLE_KEY =
|
public static final LocTextKey EXAM_FORM_TITLE_KEY = new LocTextKey("sebserver.exam.form.title");
|
||||||
new LocTextKey("sebserver.exam.form.title");
|
public static final LocTextKey EXAM_FORM_TITLE_IMPORT_KEY = new LocTextKey("sebserver.exam.form.title.import");
|
||||||
public static final LocTextKey EXAM_FORM_TITLE_IMPORT_KEY =
|
private static final LocTextKey FORM_SUPPORTER_TEXT_KEY = new LocTextKey("sebserver.exam.form.supporter");
|
||||||
new LocTextKey("sebserver.exam.form.title.import");
|
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_SUPPORTER_TEXT_KEY =
|
private static final LocTextKey FORM_TYPE_TEXT_KEY = new LocTextKey("sebserver.exam.form.type");
|
||||||
new LocTextKey("sebserver.exam.form.supporter");
|
private static final LocTextKey FORM_END_TIME_TEXT_KEY = new LocTextKey("sebserver.exam.form.endtime");
|
||||||
private static final LocTextKey FORM_STATUS_TEXT_KEY =
|
private static final LocTextKey FORM_START_TIME_TEXT_KEY = new LocTextKey("sebserver.exam.form.starttime");
|
||||||
new LocTextKey("sebserver.exam.form.status");
|
private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY = new LocTextKey("sebserver.exam.form.description");
|
||||||
private static final LocTextKey FORM_TYPE_TEXT_KEY =
|
private static final LocTextKey FORM_NAME_TEXT_KEY = new LocTextKey("sebserver.exam.form.name");
|
||||||
new LocTextKey("sebserver.exam.form.type");
|
private static final LocTextKey FORM_QUIZ_ID_TEXT_KEY = new LocTextKey("sebserver.exam.form.quizid");
|
||||||
private static final LocTextKey FORM_END_TIME_TEXT_KEY =
|
private static final LocTextKey FORM_QUIZ_URL_TEXT_KEY = new LocTextKey("sebserver.exam.form.quizurl");
|
||||||
new LocTextKey("sebserver.exam.form.endtime");
|
private static final LocTextKey FORM_LMSSETUP_TEXT_KEY = new LocTextKey("sebserver.exam.form.lmssetup");
|
||||||
private static final LocTextKey FORM_START_TIME_TEXT_KEY =
|
private final static LocTextKey ACTION_MESSAGE_SEB_RESTRICTION_RELEASE = new LocTextKey("sebserver.exam.action.sebrestriction.release.confirm");
|
||||||
new LocTextKey("sebserver.exam.form.starttime");
|
private static final LocTextKey FORM_EXAM_TEMPLATE_TEXT_KEY = new LocTextKey("sebserver.exam.form.examTemplate");
|
||||||
private static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
|
private static final LocTextKey FORM_EXAM_TEMPLATE_ERROR = new LocTextKey("sebserver.exam.form.examTemplate.error");
|
||||||
new LocTextKey("sebserver.exam.form.description");
|
private static final LocTextKey EXAM_ARCHIVE_CONFIRM = new LocTextKey("sebserver.exam.action.archive.confirm");
|
||||||
private static final LocTextKey FORM_NAME_TEXT_KEY =
|
private final static LocTextKey CONSISTENCY_MESSAGE_TITLE = new LocTextKey("sebserver.exam.consistency.title");
|
||||||
new LocTextKey("sebserver.exam.form.name");
|
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_SUPPORTER = new LocTextKey("sebserver.exam.consistency.missing-supporter");
|
||||||
private static final LocTextKey FORM_QUIZ_ID_TEXT_KEY =
|
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_INDICATOR = new LocTextKey("sebserver.exam.consistency.missing-indicator");
|
||||||
new LocTextKey("sebserver.exam.form.quizid");
|
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_CONFIG = new LocTextKey("sebserver.exam.consistency.missing-config");
|
||||||
private static final LocTextKey FORM_QUIZ_URL_TEXT_KEY =
|
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_SEB_RESTRICTION = new LocTextKey("sebserver.exam.consistency.missing-seb-restriction");
|
||||||
new LocTextKey("sebserver.exam.form.quizurl");
|
private final static LocTextKey CONSISTENCY_MESSAGE_VALIDATION_LMS_CONNECTION = new LocTextKey("sebserver.exam.consistency.no-lms-connection");
|
||||||
private static final LocTextKey FORM_LMSSETUP_TEXT_KEY =
|
private final static LocTextKey CONSISTENCY_MESSAGEINVALID_ID_REFERENCE = new LocTextKey("sebserver.exam.consistency.invalid-lms-id");
|
||||||
new LocTextKey("sebserver.exam.form.lmssetup");
|
private final static LocTextKey CONSISTENCY_MESSAGE_SEB_RESTRICTION_MISMATCH = new LocTextKey("sebserver.exam.consistencyseb-restriction-mismatch");
|
||||||
private final static LocTextKey ACTION_MESSAGE_SEB_RESTRICTION_RELEASE =
|
private final static LocTextKey AUTO_GEN_CONFIG_ERROR_TITLE = new LocTextKey("sebserver.exam.autogen.error.config.title");
|
||||||
new LocTextKey("sebserver.exam.action.sebrestriction.release.confirm");
|
private final static LocTextKey AUTO_GEN_CONFIG_ERROR_TEXT = new LocTextKey("sebserver.exam.autogen.error.config.text");
|
||||||
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 Map<String, LocTextKey> consistencyMessageMapping;
|
||||||
private final PageService pageService;
|
private final PageService pageService;
|
||||||
|
@ -507,13 +479,20 @@ public class ExamForm implements TemplateComposer {
|
||||||
.withInputSpan(7)
|
.withInputSpan(7)
|
||||||
.withEmptyCellSeparation(false))
|
.withEmptyCellSeparation(false))
|
||||||
|
|
||||||
|
.addField(FormBuilder.password(
|
||||||
|
Domain.EXAM.ATTR_QUIT_PASSWORD,
|
||||||
|
FORM_QUIT_PWD_TEXT_KEY,
|
||||||
|
exam.quitPassword)
|
||||||
|
.withInputSpan(3)
|
||||||
|
.withEmptyCellSeparation(false))
|
||||||
|
|
||||||
.addField(FormBuilder.multiComboSelection(
|
.addField(FormBuilder.multiComboSelection(
|
||||||
Domain.EXAM.ATTR_SUPPORTER,
|
Domain.EXAM.ATTR_SUPPORTER,
|
||||||
FORM_SUPPORTER_TEXT_KEY,
|
FORM_SUPPORTER_TEXT_KEY,
|
||||||
StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR),
|
StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR),
|
||||||
this.resourceService::examSupporterResources)
|
this.resourceService::examSupporterResources)
|
||||||
.withInputSpan(7)
|
.withInputSpan(7)
|
||||||
.withEmptyCellSeparation(false))
|
.withEmptyCellSpan(4))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,12 +594,19 @@ public class ExamForm implements TemplateComposer {
|
||||||
this.resourceService::examTypeResources)
|
this.resourceService::examTypeResources)
|
||||||
.mandatory(true))
|
.mandatory(true))
|
||||||
|
|
||||||
|
.addField(FormBuilder.password(
|
||||||
|
Domain.EXAM.ATTR_QUIT_PASSWORD,
|
||||||
|
FORM_QUIT_PWD_TEXT_KEY,
|
||||||
|
exam.quitPassword))
|
||||||
|
|
||||||
.addField(FormBuilder.multiComboSelection(
|
.addField(FormBuilder.multiComboSelection(
|
||||||
Domain.EXAM.ATTR_SUPPORTER,
|
Domain.EXAM.ATTR_SUPPORTER,
|
||||||
FORM_SUPPORTER_TEXT_KEY,
|
FORM_SUPPORTER_TEXT_KEY,
|
||||||
StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR),
|
StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR),
|
||||||
this.resourceService::examSupporterResources))
|
this.resourceService::examSupporterResources))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.buildFor(importFromLMS
|
.buildFor(importFromLMS
|
||||||
? this.restService.getRestCall(ImportAsExam.class)
|
? this.restService.getRestCall(ImportAsExam.class)
|
||||||
: newExam
|
: newExam
|
||||||
|
@ -643,6 +629,7 @@ public class ExamForm implements TemplateComposer {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
ExamStatus.UP_COMING,
|
ExamStatus.UP_COMING,
|
||||||
|
null,
|
||||||
false,
|
false,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
|
@ -679,13 +666,16 @@ public class ExamForm implements TemplateComposer {
|
||||||
.call()
|
.call()
|
||||||
.getOrThrow();
|
.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_TYPE, examTemplate.examType.name());
|
||||||
form.setFieldValue(
|
form.setFieldValue(
|
||||||
Domain.EXAM.ATTR_SUPPORTER,
|
Domain.EXAM.ATTR_SUPPORTER,
|
||||||
StringUtils.join(examTemplate.supporter, Constants.LIST_SEPARATOR));
|
StringUtils.join(examTemplate.supporter, Constants.LIST_SEPARATOR));
|
||||||
|
form.setFieldValue(Domain.EXAM.ATTR_QUIT_PASSWORD, quitPassword);
|
||||||
} else {
|
} else {
|
||||||
form.setFieldValue(Domain.EXAM.ATTR_TYPE, Exam.ExamType.UNDEFINED.name());
|
form.setFieldValue(Domain.EXAM.ATTR_TYPE, Exam.ExamType.UNDEFINED.name());
|
||||||
form.setFieldValue(Domain.EXAM.ATTR_SUPPORTER, null);
|
form.setFieldValue(Domain.EXAM.ATTR_SUPPORTER, null);
|
||||||
|
form.setFieldValue(Domain.EXAM.ATTR_QUIT_PASSWORD, null);
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
context.notifyError(FORM_EXAM_TEMPLATE_ERROR, e);
|
context.notifyError(FORM_EXAM_TEMPLATE_ERROR, e);
|
||||||
|
|
|
@ -227,4 +227,5 @@ public interface ExamDAO extends ActivatableEntityDAO<Exam, Exam>, BulkActionSup
|
||||||
* @param updateId The update identifier given by the update task */
|
* @param updateId The update identifier given by the update task */
|
||||||
void markLMSAvailability(final String externalQuizId, final boolean available, final String updateId);
|
void markLMSAvailability(final String externalQuizId, final boolean available, final String updateId);
|
||||||
|
|
||||||
|
void updateQuitPassword(Exam exam, String quitPassword);
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,6 +195,13 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
.onError(error -> log.error("Failed to mark LMS not available: {}", externalQuizId, error));
|
.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
|
@Override
|
||||||
public Result<Exam> setSEBRestriction(final Long examId, final boolean sebRestriction) {
|
public Result<Exam> setSEBRestriction(final Long examId, final boolean sebRestriction) {
|
||||||
return this.examRecordDAO
|
return this.examRecordDAO
|
||||||
|
@ -789,6 +796,7 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
record.getOwner(),
|
record.getOwner(),
|
||||||
supporter,
|
supporter,
|
||||||
status,
|
status,
|
||||||
|
record.getQuitPassword(),
|
||||||
BooleanUtils.toBooleanObject(record.getLmsSebRestriction()),
|
BooleanUtils.toBooleanObject(record.getLmsSebRestriction()),
|
||||||
record.getBrowserKeys(),
|
record.getBrowserKeys(),
|
||||||
BooleanUtils.toBooleanObject(record.getActive()),
|
BooleanUtils.toBooleanObject(record.getActive()),
|
||||||
|
|
|
@ -9,15 +9,13 @@
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl;
|
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.*;
|
||||||
|
import static ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport.quitPassword;
|
||||||
import static org.mybatis.dynamic.sql.SqlBuilder.*;
|
import static org.mybatis.dynamic.sql.SqlBuilder.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
@ -63,13 +61,16 @@ public class ExamRecordDAO {
|
||||||
|
|
||||||
private final ExamRecordMapper examRecordMapper;
|
private final ExamRecordMapper examRecordMapper;
|
||||||
private final ClientConnectionRecordMapper clientConnectionRecordMapper;
|
private final ClientConnectionRecordMapper clientConnectionRecordMapper;
|
||||||
|
private final Cryptor cryptor;
|
||||||
|
|
||||||
public ExamRecordDAO(
|
public ExamRecordDAO(
|
||||||
final ExamRecordMapper examRecordMapper,
|
final ExamRecordMapper examRecordMapper,
|
||||||
final ClientConnectionRecordMapper clientConnectionRecordMapper) {
|
final ClientConnectionRecordMapper clientConnectionRecordMapper,
|
||||||
|
final Cryptor cryptor) {
|
||||||
|
|
||||||
this.examRecordMapper = examRecordMapper;
|
this.examRecordMapper = examRecordMapper;
|
||||||
this.clientConnectionRecordMapper = clientConnectionRecordMapper;
|
this.clientConnectionRecordMapper = clientConnectionRecordMapper;
|
||||||
|
this.cryptor = cryptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
|
@ -136,7 +137,7 @@ public class ExamRecordDAO {
|
||||||
.build()
|
.build()
|
||||||
.execute()
|
.execute()
|
||||||
.stream()
|
.stream()
|
||||||
.map(rec -> rec.getInstitutionId())
|
.map(ExamRecord::getInstitutionId)
|
||||||
.collect(Collectors.toList());
|
.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
|
@Transactional
|
||||||
public Result<ExamRecord> updateState(final Long examId, final ExamStatus status, final String updateId) {
|
public Result<ExamRecord> updateState(final Long examId, final ExamStatus status, final String updateId) {
|
||||||
return recordById(examId)
|
return recordById(examId)
|
||||||
|
@ -270,41 +298,39 @@ public class ExamRecordDAO {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exam.status != null && !exam.status.name().equals(oldRecord.getStatus())) {
|
if (exam.status != null && !exam.status.name().equals(oldRecord.getStatus())) {
|
||||||
log.info("Exam state change on save. Exam. {}, Old state: {}, new state: {}",
|
log.info(
|
||||||
exam.externalId,
|
"Exam state change on save. Exam. {}, Old state: {}, new state: {}",
|
||||||
oldRecord.getStatus(),
|
exam.externalId,
|
||||||
exam.status);
|
oldRecord.getStatus(),
|
||||||
|
exam.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
final ExamRecord examRecord = new ExamRecord(
|
UpdateDSL.updateWithMapper(examRecordMapper::update, examRecord)
|
||||||
exam.id,
|
.set(supporter).equalTo((exam.supporter != null)
|
||||||
null, null, null, null,
|
? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
|
||||||
(exam.supporter != null)
|
: null)
|
||||||
? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
|
.set(type).equalTo((exam.type != null)
|
||||||
: null,
|
? exam.type.name()
|
||||||
(exam.type != null)
|
: ExamType.UNDEFINED.name())
|
||||||
? exam.type.name()
|
.set(quitPassword).equalTo(getEncryptedQuitPassword(exam.quitPassword))
|
||||||
: null,
|
.set(browserKeys).equalToWhenPresent(exam.browserExamKeys)
|
||||||
null,
|
.set(lmsSebRestriction).equalTo(1) // seb restriction (deprecated)
|
||||||
exam.browserExamKeys,
|
.set(examTemplateId).equalTo(exam.examTemplateId)
|
||||||
null,
|
.set(lastModified).equalTo(Utils.getMillisecondsNow())
|
||||||
1, // seb restriction (deprecated)
|
.set(quizName).equalToWhenPresent(exam.lmsSetupId == null ? exam.name : null)
|
||||||
null, // updating
|
.set(quizStartTime).equalToWhenPresent(exam.lmsSetupId == null ? exam.startTime : null)
|
||||||
null, // lastUpdate
|
.set(quizEndTime).equalToWhenPresent(exam.lmsSetupId == null ? exam.endTime : null)
|
||||||
null, // active
|
.where(id, isEqualTo(exam.id))
|
||||||
exam.examTemplateId,
|
.build()
|
||||||
Utils.getMillisecondsNow(),
|
.execute();
|
||||||
exam.lmsSetupId == null ? exam.name : null,
|
|
||||||
exam.lmsSetupId == null ? exam.startTime : null,
|
|
||||||
exam.lmsSetupId == null ? exam.endTime : null,
|
|
||||||
null);
|
|
||||||
|
|
||||||
this.examRecordMapper.updateByPrimaryKeySelective(examRecord);
|
|
||||||
return this.examRecordMapper.selectByPrimaryKey(exam.id);
|
return this.examRecordMapper.selectByPrimaryKey(exam.id);
|
||||||
})
|
})
|
||||||
.onError(TransactionHandler::rollback);
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<ExamRecord> updateFromQuizData(
|
public Result<ExamRecord> updateFromQuizData(
|
||||||
final Long examId,
|
final Long examId,
|
||||||
|
@ -445,7 +471,7 @@ public class ExamRecordDAO {
|
||||||
? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
|
? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
|
||||||
: null,
|
: null,
|
||||||
(exam.type != null) ? exam.type.name() : ExamType.UNDEFINED.name(),
|
(exam.type != null) ? exam.type.name() : ExamType.UNDEFINED.name(),
|
||||||
null, // quitPassword
|
getEncryptedQuitPassword(exam.quitPassword),
|
||||||
null, // browser keys
|
null, // browser keys
|
||||||
(exam.status != null) ? exam.status.name() : ExamStatus.UP_COMING.name(),
|
(exam.status != null) ? exam.status.name() : ExamStatus.UP_COMING.name(),
|
||||||
1, // seb restriction (deprecated)
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,6 +210,15 @@ public class ExamTemplateDAOImpl implements ExamTemplateDAO {
|
||||||
indicatorsJSON,
|
indicatorsJSON,
|
||||||
BooleanUtils.toInteger(data.institutionalDefault));
|
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);
|
this.examTemplateRecordMapper.insert(newRecord);
|
||||||
return newRecord;
|
return newRecord;
|
||||||
})
|
})
|
||||||
|
|
|
@ -163,6 +163,8 @@ public interface ExamAdminService {
|
||||||
* @param exam the exam that has been changed and saved */
|
* @param exam the exam that has been changed and saved */
|
||||||
void notifyExamSaved(Exam exam);
|
void notifyExamSaved(Exam exam);
|
||||||
|
|
||||||
|
void applyQuitPassword(Exam entity);
|
||||||
|
|
||||||
static void newExamFieldValidation(final POSTMapper postParams) {
|
static void newExamFieldValidation(final POSTMapper postParams) {
|
||||||
noLMSFieldValidation(new Exam(postParams));
|
noLMSFieldValidation(new Exam(postParams));
|
||||||
}
|
}
|
||||||
|
@ -337,5 +339,4 @@ public interface ExamAdminService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,13 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.exam;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.exam;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
|
||||||
public interface ExamConfigurationValueService {
|
public interface ExamConfigurationValueService {
|
||||||
|
|
||||||
public static final String CONFIG_ATTR_NAME_QUIT_LINK = "quitURL";
|
String CONFIG_ATTR_NAME_QUIT_LINK = "quitURL";
|
||||||
public static final String CONFIG_ATTR_NAME_QUIT_SECRET = "hashedQuitPassword";
|
String CONFIG_ATTR_NAME_QUIT_SECRET = "hashedQuitPassword";
|
||||||
public static final String CONFIG_ATTR_NAME_ALLOWED_SEB_VERSION = "sebAllowedVersions";
|
String CONFIG_ATTR_NAME_ALLOWED_SEB_VERSION = "sebAllowedVersions";
|
||||||
|
|
||||||
/** Get the actual SEB settings attribute value for the exam configuration mapped as default configuration
|
/** Get the actual SEB settings attribute value for the exam configuration mapped as default configuration
|
||||||
* to the given exam
|
* to the given exam
|
||||||
|
@ -29,7 +31,7 @@ public interface ExamConfigurationValueService {
|
||||||
*
|
*
|
||||||
* @param examId The exam identifier
|
* @param examId The exam identifier
|
||||||
* @param configAttributeName The name of the SEB settings attribute
|
* @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. */
|
* @return The current value of the above SEB settings attribute and given exam. */
|
||||||
String getMappedDefaultConfigAttributeValue(
|
String getMappedDefaultConfigAttributeValue(
|
||||||
Long examId,
|
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.
|
/** Get the quitPassword SEB Setting from the Exam Configuration that is applied to the given exam.
|
||||||
*
|
*
|
||||||
* @param examId Exam identifier
|
* @param examId Exam identifier
|
||||||
* @return the vlaue of the quitPassword SEB Setting */
|
* @return the value of the quitPassword SEB Setting */
|
||||||
String getQuitSecret(Long examId);
|
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.
|
/** Get the quitLink SEB Setting from the Exam Configuration that is applied to the given exam.
|
||||||
*
|
*
|
||||||
|
|
|
@ -328,6 +328,13 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
this.proctoringAdminService.notifyExamSaved(exam);
|
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) {
|
private Result<Exam> initAdditionalAttributesForMoodleExams(final Exam exam) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,11 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
|
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.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -59,24 +64,16 @@ public class ExamConfigurationValueServiceImpl implements ExamConfigurationValue
|
||||||
|
|
||||||
final Long configId = this.examConfigurationMapDAO
|
final Long configId = this.examConfigurationMapDAO
|
||||||
.getDefaultConfigurationNode(examId)
|
.getDefaultConfigurationNode(examId)
|
||||||
.flatMap(nodeId -> this.configurationDAO.getConfigurationLastStableVersion(nodeId))
|
.flatMap(this.configurationDAO::getConfigurationLastStableVersion)
|
||||||
.map(config -> config.id)
|
.map(config -> config.id)
|
||||||
.onError(error -> log.warn("Failed to get default Exam Config for exam: {} cause: {}",
|
|
||||||
examId, error.getMessage()))
|
|
||||||
.getOr(null);
|
.getOr(null);
|
||||||
|
|
||||||
if (configId == null) {
|
if (configId == null) {
|
||||||
return defaultValue;
|
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
|
return this.configurationValueDAO
|
||||||
.getConfigAttributeValue(configId, attrId)
|
.getConfigAttributeValue(configId, getAttributeId(configAttributeName))
|
||||||
.onError(error -> log.warn(
|
.onError(error -> log.warn(
|
||||||
"Failed to get exam config attribute: {} {} error: {}",
|
"Failed to get exam config attribute: {} {} error: {}",
|
||||||
examId,
|
examId,
|
||||||
|
@ -92,8 +89,10 @@ public class ExamConfigurationValueServiceImpl implements ExamConfigurationValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getQuitSecret(final Long examId) {
|
public String getQuitPassword(final Long examId) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final String quitSecretEncrypted = getMappedDefaultConfigAttributeValue(
|
final String quitSecretEncrypted = getMappedDefaultConfigAttributeValue(
|
||||||
|
@ -119,6 +118,86 @@ public class ExamConfigurationValueServiceImpl implements ExamConfigurationValue
|
||||||
return StringUtils.EMPTY;
|
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
|
@Override
|
||||||
public String getQuitLink(final Long examId) {
|
public String getQuitLink(final Long examId) {
|
||||||
try {
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,7 +228,6 @@ public class ExamTemplateServiceImpl implements ExamTemplateService {
|
||||||
.getOrThrow(error -> new APIMessageException(
|
.getOrThrow(error -> new APIMessageException(
|
||||||
ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CONFIG_LINKING,
|
ErrorMessage.EXAM_IMPORT_ERROR_AUTO_CONFIG_LINKING,
|
||||||
error));
|
error));
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
|
|
|
@ -165,7 +165,6 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public Result<Exam> saveSEBRestrictionToExam(final Exam exam, final SEBRestriction sebRestriction) {
|
public Result<Exam> saveSEBRestrictionToExam(final Exam exam, final SEBRestriction sebRestriction) {
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
|
@ -181,6 +180,7 @@ public class SEBRestrictionServiceImpl implements SEBRestrictionService {
|
||||||
exam.supporter,
|
exam.supporter,
|
||||||
exam.status,
|
exam.status,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
(browserExamKeys != null && !browserExamKeys.isEmpty())
|
(browserExamKeys != null && !browserExamKeys.isEmpty())
|
||||||
? StringUtils.join(browserExamKeys, Constants.LIST_SEPARATOR_CHAR)
|
? StringUtils.join(browserExamKeys, Constants.LIST_SEPARATOR_CHAR)
|
||||||
: StringUtils.EMPTY,
|
: StringUtils.EMPTY,
|
||||||
|
|
|
@ -136,7 +136,7 @@ public class MoodlePluginCourseRestriction implements SEBRestrictionAPI {
|
||||||
final ArrayList<String> beks = new ArrayList<>(sebRestrictionData.browserExamKeys);
|
final ArrayList<String> beks = new ArrayList<>(sebRestrictionData.browserExamKeys);
|
||||||
final ArrayList<String> configKeys = new ArrayList<>(sebRestrictionData.configKeys);
|
final ArrayList<String> configKeys = new ArrayList<>(sebRestrictionData.configKeys);
|
||||||
final String quitLink = this.examConfigurationValueService.getQuitLink(exam.id);
|
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(
|
final String additionalBEK = exam.getAdditionalAttribute(
|
||||||
SEBRestrictionService.ADDITIONAL_ATTR_ALTERNATIVE_SEB_BEK);
|
SEBRestrictionService.ADDITIONAL_ATTR_ALTERNATIVE_SEB_BEK);
|
||||||
|
|
||||||
|
|
|
@ -368,7 +368,7 @@ public class OlatLmsAPITemplate extends AbstractCachedCourseAccess implements Lm
|
||||||
post.configKeys = new ArrayList<>(restriction.configKeys);
|
post.configKeys = new ArrayList<>(restriction.configKeys);
|
||||||
if (this.restrictWithAdditionalAttributes) {
|
if (this.restrictWithAdditionalAttributes) {
|
||||||
post.quitLink = this.examConfigurationValueService.getQuitLink(restriction.examId);
|
post.quitLink = this.examConfigurationValueService.getQuitLink(restriction.examId);
|
||||||
post.quitSecret = this.examConfigurationValueService.getQuitSecret(restriction.examId);
|
post.quitSecret = this.examConfigurationValueService.getQuitPassword(restriction.examId);
|
||||||
}
|
}
|
||||||
final RestrictionData r =
|
final RestrictionData r =
|
||||||
this.apiPost(restTemplate, url, post, RestrictionDataPost.class, RestrictionData.class);
|
this.apiPost(restTemplate, url, post, RestrictionDataPost.class, RestrictionData.class);
|
||||||
|
|
|
@ -18,32 +18,32 @@ public interface ExamConfigUpdateService {
|
||||||
|
|
||||||
/** Used to process a SEB Exam Configuration change that can also effect some
|
/** Used to process a SEB Exam Configuration change that can also effect some
|
||||||
* running exams that as the specified configuration attached.
|
* running exams that as the specified configuration attached.
|
||||||
*
|
* <p>
|
||||||
* This deals with the whole functionality the underling data-structure provides. So
|
* 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
|
* 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.
|
* 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
|
* 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
|
* 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
|
* established connections that are not yet closed and connection attempts that are older the a
|
||||||
* defined time interval.
|
* defined time interval.
|
||||||
*
|
* <p>
|
||||||
* After this check passed, the system places an update-lock on each Exam that is involved on the
|
* 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.
|
* attempts to be allowed.
|
||||||
*
|
* <p>
|
||||||
* After placing update-locks the fist check is done again to ensure there where no new SEB Client
|
* 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
|
* connection attempts in the meantime. If there where, the procedure will stop and rollback all
|
||||||
* changes so far.
|
* changes so far.
|
||||||
*
|
* <p>
|
||||||
* If everything is locked the changes to the SEB Exam Configuration will be saved to the data-base
|
* 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.
|
* 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
|
* 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
|
* 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
|
* rollback of the entire procedure is applied. Instead the error is logged and the update can be
|
||||||
* later triggered manually by an administrator.
|
* 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
|
* 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.
|
* the update-locks is applied to ensure all involved Exams are not locked anymore.
|
||||||
*
|
*
|
||||||
|
|
|
@ -33,9 +33,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.ExamSessionCac
|
||||||
/** A Service to handle running exam sessions */
|
/** A Service to handle running exam sessions */
|
||||||
public interface ExamSessionService {
|
public interface ExamSessionService {
|
||||||
|
|
||||||
public static final Predicate<ClientConnection> ACTIVE_CONNECTION_FILTER =
|
Predicate<ClientConnection> ACTIVE_CONNECTION_FILTER =
|
||||||
cc -> cc.status == ConnectionStatus.ACTIVE;
|
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;
|
ccd -> ccd.clientConnection.status == ConnectionStatus.ACTIVE;
|
||||||
|
|
||||||
/** Get the underling ExamDAO service.
|
/** Get the underling ExamDAO service.
|
||||||
|
|
|
@ -13,6 +13,8 @@ import java.util.Collections;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
@ -46,6 +48,8 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
|
||||||
private final ExamSessionService examSessionService;
|
private final ExamSessionService examSessionService;
|
||||||
private final ExamUpdateHandler examUpdateHandler;
|
private final ExamUpdateHandler examUpdateHandler;
|
||||||
private final ExamAdminService examAdminService;
|
private final ExamAdminService examAdminService;
|
||||||
|
private final ExamConfigurationValueService examConfigurationValueService;
|
||||||
|
|
||||||
|
|
||||||
protected ExamConfigUpdateServiceImpl(
|
protected ExamConfigUpdateServiceImpl(
|
||||||
final ExamDAO examDAO,
|
final ExamDAO examDAO,
|
||||||
|
@ -53,7 +57,8 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
|
||||||
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
||||||
final ExamSessionService examSessionService,
|
final ExamSessionService examSessionService,
|
||||||
final ExamUpdateHandler examUpdateHandler,
|
final ExamUpdateHandler examUpdateHandler,
|
||||||
final ExamAdminService examAdminService) {
|
final ExamAdminService examAdminService,
|
||||||
|
final ExamConfigurationValueService examConfigurationValueService) {
|
||||||
|
|
||||||
this.examDAO = examDAO;
|
this.examDAO = examDAO;
|
||||||
this.configurationDAO = configurationDAO;
|
this.configurationDAO = configurationDAO;
|
||||||
|
@ -61,13 +66,15 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
|
||||||
this.examSessionService = examSessionService;
|
this.examSessionService = examSessionService;
|
||||||
this.examUpdateHandler = examUpdateHandler;
|
this.examUpdateHandler = examUpdateHandler;
|
||||||
this.examAdminService = examAdminService;
|
this.examAdminService = examAdminService;
|
||||||
|
this.examConfigurationValueService = examConfigurationValueService;
|
||||||
}
|
}
|
||||||
|
|
||||||
// processing:
|
// processing:
|
||||||
// check running exam integrity (No running exam with active SEB client-connection available)
|
// 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)
|
// 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
|
// 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)
|
// 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
|
// evict each Exam from cache and release the update-lock on DB
|
||||||
@Override
|
@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)
|
// 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) {
|
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) {
|
if (exam.getStatus() == ExamStatus.RUNNING && exam.lmsSetupId != null) {
|
||||||
|
|
||||||
this.examUpdateHandler
|
this.examUpdateHandler
|
||||||
|
@ -202,6 +213,15 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
|
||||||
mapping.configurationNodeId))
|
mapping.configurationNodeId))
|
||||||
.getOrThrow();
|
.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
|
// update seb client restriction if the feature is activated for the exam
|
||||||
this.examUpdateHandler
|
this.examUpdateHandler
|
||||||
.getSEBRestrictionService()
|
.getSEBRestrictionService()
|
||||||
|
|
|
@ -339,7 +339,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
return;
|
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 (this.distributedSetup && !this.examSessionCacheService.isUpToDate(sebConfigForExam)) {
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
|
@ -530,9 +530,8 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
@Override
|
@Override
|
||||||
public Result<Exam> updateExamCache(final Long examId) {
|
public Result<Exam> updateExamCache(final Long examId) {
|
||||||
|
|
||||||
// TODO check how often this is called in distributed environments
|
// TODO make interval access. this should only check when the last check was more then 5 seconds ago
|
||||||
//System.out.println("************** performance check: updateExamCache");
|
// TODO is this really needed?
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final Cache cache = this.cacheManager.getCache(ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM);
|
final Cache cache = this.cacheManager.getCache(ExamSessionCacheService.CACHE_NAME_RUNNING_EXAM);
|
||||||
final ValueWrapper valueWrapper = cache.get(examId);
|
final ValueWrapper valueWrapper = cache.get(examId);
|
||||||
|
@ -554,7 +553,6 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
.getOr(false);
|
.getOr(false);
|
||||||
|
|
||||||
if (!BooleanUtils.toBoolean(isUpToDate)) {
|
if (!BooleanUtils.toBoolean(isUpToDate)) {
|
||||||
// TODO this should only flush the exam cache but not the SEB connection cache
|
|
||||||
return flushCache(exam);
|
return flushCache(exam);
|
||||||
} else {
|
} else {
|
||||||
return Result.of(exam);
|
return Result.of(exam);
|
||||||
|
@ -603,7 +601,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
this.clientConnectionDAO.getClientConnectionsOutOfSyc(examId, timestamps)
|
this.clientConnectionDAO.getClientConnectionsOutOfSyc(examId, timestamps)
|
||||||
.getOrElse(() -> Collections.emptySet())
|
.getOrElse(Collections::emptySet)
|
||||||
.stream()
|
.stream()
|
||||||
.forEach(this.examSessionCacheService::evictClientConnection);
|
.forEach(this.examSessionCacheService::evictClientConnection);
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
@ -652,6 +653,8 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
return entity;
|
return entity;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.examAdminService.applyQuitPassword(entity);
|
||||||
|
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
errors.add(0, ErrorMessage.EXAM_IMPORT_ERROR_AUTO_SETUP.of(
|
errors.add(0, ErrorMessage.EXAM_IMPORT_ERROR_AUTO_SETUP.of(
|
||||||
entity.getModelId(),
|
entity.getModelId(),
|
||||||
|
@ -669,6 +672,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
protected Result<Exam> notifySaved(final Exam entity) {
|
protected Result<Exam> notifySaved(final Exam entity) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
this.examAdminService.notifyExamSaved(entity);
|
this.examAdminService.notifyExamSaved(entity);
|
||||||
|
this.examAdminService.applyQuitPassword(entity);
|
||||||
this.examSessionService.flushCache(entity);
|
this.examSessionService.flushCache(entity);
|
||||||
return entity;
|
return entity;
|
||||||
});
|
});
|
||||||
|
@ -684,7 +688,8 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
protected Result<Exam> validForSave(final Exam entity) {
|
protected Result<Exam> validForSave(final Exam entity) {
|
||||||
return super.validForSave(entity)
|
return super.validForSave(entity)
|
||||||
.map(this::checkExamSupporterRole)
|
.map(this::checkExamSupporterRole)
|
||||||
.map(ExamAdminService::noLMSFieldValidation);
|
.map(ExamAdminService::noLMSFieldValidation)
|
||||||
|
.map(this::checkQuitPasswordChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -702,6 +707,22 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
return checkNoActiveSEBClientConnections(entity);
|
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) {
|
private Exam checkExamSupporterRole(final Exam exam) {
|
||||||
final Set<String> examSupporter = this.userDAO.all(
|
final Set<String> examSupporter = this.userDAO.all(
|
||||||
this.authorization.getUserService().getCurrentUser().getUserInfo().institutionId,
|
this.authorization.getUserService().getCurrentUser().getUserInfo().institutionId,
|
||||||
|
|
|
@ -8,18 +8,17 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService;
|
||||||
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.LoggerFactory;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
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)
|
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_TEMPLATE_ENDPOINT)
|
||||||
public class ExamTemplateController extends EntityController<ExamTemplate, ExamTemplate> {
|
public class ExamTemplateController extends EntityController<ExamTemplate, ExamTemplate> {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ExamTemplateController.class);
|
||||||
|
|
||||||
private final ExamTemplateDAO examTemplateDAO;
|
private final ExamTemplateDAO examTemplateDAO;
|
||||||
private final ProctoringAdminService proctoringServiceSettingsService;
|
private final ProctoringAdminService proctoringServiceSettingsService;
|
||||||
|
private final ExamConfigurationValueService examConfigurationValueService;
|
||||||
|
|
||||||
protected ExamTemplateController(
|
protected ExamTemplateController(
|
||||||
final AuthorizationService authorization,
|
final AuthorizationService authorization,
|
||||||
|
@ -70,7 +72,8 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
|
||||||
final UserActivityLogDAO userActivityLogDAO,
|
final UserActivityLogDAO userActivityLogDAO,
|
||||||
final PaginationService paginationService,
|
final PaginationService paginationService,
|
||||||
final BeanValidationService beanValidationService,
|
final BeanValidationService beanValidationService,
|
||||||
final ProctoringAdminService proctoringServiceSettingsService) {
|
final ProctoringAdminService proctoringServiceSettingsService,
|
||||||
|
final ExamConfigurationValueService examConfigurationValueService) {
|
||||||
|
|
||||||
super(
|
super(
|
||||||
authorization,
|
authorization,
|
||||||
|
@ -82,6 +85,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
|
||||||
|
|
||||||
this.examTemplateDAO = entityDAO;
|
this.examTemplateDAO = entityDAO;
|
||||||
this.proctoringServiceSettingsService = proctoringServiceSettingsService;
|
this.proctoringServiceSettingsService = proctoringServiceSettingsService;
|
||||||
|
this.examConfigurationValueService = examConfigurationValueService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(
|
@RequestMapping(
|
||||||
|
@ -101,6 +105,45 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
|
||||||
.getOrThrow();
|
.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
|
// **** Indicator Templates
|
||||||
|
|
||||||
|
@ -466,7 +509,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
|
||||||
|
|
||||||
final String sortBy = PageSortOrder.decode(sort);
|
final String sortBy = PageSortOrder.decode(sort);
|
||||||
return indicators -> {
|
return indicators -> {
|
||||||
final List<IndicatorTemplate> list = indicators.stream().collect(Collectors.toList());
|
final List<IndicatorTemplate> list = new ArrayList<>(indicators);
|
||||||
if (StringUtils.isBlank(sort)) {
|
if (StringUtils.isBlank(sort)) {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
@ -487,7 +530,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
|
||||||
|
|
||||||
final String sortBy = PageSortOrder.decode(sort);
|
final String sortBy = PageSortOrder.decode(sort);
|
||||||
return clientGroups -> {
|
return clientGroups -> {
|
||||||
final List<ClientGroupTemplate> list = clientGroups.stream().collect(Collectors.toList());
|
final List<ClientGroupTemplate> list = new ArrayList<>(clientGroups);
|
||||||
if (StringUtils.isBlank(sort)) {
|
if (StringUtils.isBlank(sort)) {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
server.address=localhost
|
server.address=localhost
|
||||||
server.port=8080
|
server.port=8090
|
||||||
|
|
||||||
sebserver.gui.http.external.scheme=http
|
sebserver.gui.http.external.scheme=http
|
||||||
sebserver.gui.entrypoint=/gui
|
sebserver.gui.entrypoint=/gui
|
||||||
sebserver.gui.webservice.protocol=http
|
sebserver.gui.webservice.protocol=http
|
||||||
sebserver.gui.webservice.address=localhost
|
sebserver.gui.webservice.address=localhost
|
||||||
sebserver.gui.webservice.port=8080
|
sebserver.gui.webservice.port=8090
|
||||||
sebserver.gui.webservice.apipath=/admin-api/v1
|
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
|
# 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
|
sebserver.gui.webservice.poll-interval=1000
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
-- ----------------------------------------------------------------
|
||||||
|
-- Add SEB Settings GUI additions (SEBSERV-414 and SEBSERV-465)
|
||||||
|
-- ----------------------------------------------------------------
|
||||||
|
|
|
@ -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=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.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.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.popup.title=Export Connection Configuration for Starting the Exam
|
||||||
sebserver.exam.form.export.config.name=Name
|
sebserver.exam.form.export.config.name=Name
|
||||||
|
|
|
@ -205,7 +205,7 @@ public class ModelObjectJSONGenerator {
|
||||||
1L, 1L, 1L, "externalId", true, "name", DateTime.now(), DateTime.now(),
|
1L, 1L, 1L, "externalId", true, "name", DateTime.now(), DateTime.now(),
|
||||||
ExamType.BYOD, "owner",
|
ExamType.BYOD, "owner",
|
||||||
Arrays.asList("user1", "user2"),
|
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(domainObject.getClass().getSimpleName() + ":");
|
||||||
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));
|
System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject));
|
||||||
|
|
||||||
|
|
|
@ -904,6 +904,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
||||||
null,
|
null,
|
||||||
Utils.immutableCollectionOf(userId),
|
Utils.immutableCollectionOf(userId),
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
false,
|
false,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
|
|
|
@ -62,6 +62,7 @@ public class ExamAPITest extends AdministrationAPIIntegrationTester {
|
||||||
exam.owner,
|
exam.owner,
|
||||||
Arrays.asList("user5"),
|
Arrays.asList("user5"),
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
false,
|
false,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
|
@ -91,6 +92,7 @@ public class ExamAPITest extends AdministrationAPIIntegrationTester {
|
||||||
exam.owner,
|
exam.owner,
|
||||||
Arrays.asList("user2"),
|
Arrays.asList("user2"),
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
false,
|
false,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
|
|
|
@ -91,6 +91,7 @@ public class OlatLmsAPITemplateTest extends AdministrationAPIIntegrationTester {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
ExamStatus.FINISHED,
|
ExamStatus.FINISHED,
|
||||||
|
null,
|
||||||
Boolean.FALSE,
|
Boolean.FALSE,
|
||||||
null,
|
null,
|
||||||
Boolean.FALSE,
|
Boolean.FALSE,
|
||||||
|
|
|
@ -121,7 +121,8 @@ public class SEBClientEventCSVExporterTest {
|
||||||
final ClientEventRecord event = new ClientEventRecord(0L, 1L, 2, 3L, 4L, new BigDecimal(5), "text");
|
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),
|
final Exam exam = new Exam(0L, 1L, 3L, "externalid", true, "name", new DateTime(1L),
|
||||||
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);
|
"lastUpdate", 4L, null, attrs);
|
||||||
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
final BufferedOutputStream output = new BufferedOutputStream(stream);
|
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 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),
|
final Exam exam = new Exam(0L, 1L, 3L, "externalid", true, "name", new DateTime(1L),
|
||||||
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);
|
"lastUpdate", 4L, null, attrs);
|
||||||
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
final BufferedOutputStream output = new BufferedOutputStream(stream);
|
final BufferedOutputStream output = new BufferedOutputStream(stream);
|
||||||
|
|
|
@ -52,7 +52,8 @@ public class MoodlePluginCourseRestrictionTest {
|
||||||
public void getNoneExistingRestriction() {
|
public void getNoneExistingRestriction() {
|
||||||
final MoodlePluginCourseRestriction candidate = crateMockup();
|
final MoodlePluginCourseRestriction candidate = crateMockup();
|
||||||
final Exam exam = new Exam(1L, 1L, 1L, "101:1:c1:i1",
|
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);
|
final Result<SEBRestriction> sebClientRestriction = candidate.getSEBClientRestriction(exam);
|
||||||
|
|
||||||
|
@ -67,7 +68,8 @@ public class MoodlePluginCourseRestrictionTest {
|
||||||
public void getSetGetRestriction() {
|
public void getSetGetRestriction() {
|
||||||
final MoodlePluginCourseRestriction candidate = crateMockup();
|
final MoodlePluginCourseRestriction candidate = crateMockup();
|
||||||
final Exam exam = new Exam(1L, 1L, 1L, "101:1:c1:i1",
|
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(
|
final SEBRestriction restriction = new SEBRestriction(
|
||||||
exam.id,
|
exam.id,
|
||||||
|
@ -156,7 +158,7 @@ public class MoodlePluginCourseRestrictionTest {
|
||||||
final ExamConfigurationValueService examConfigurationValueService =
|
final ExamConfigurationValueService examConfigurationValueService =
|
||||||
Mockito.mock(ExamConfigurationValueService.class);
|
Mockito.mock(ExamConfigurationValueService.class);
|
||||||
Mockito.when(examConfigurationValueService.getQuitLink(Mockito.anyLong())).thenReturn("quitLink");
|
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,
|
return new MoodlePluginCourseRestriction(jsonMapper, moodleMockupRestTemplateFactory,
|
||||||
examConfigurationValueService);
|
examConfigurationValueService);
|
||||||
|
|
Loading…
Reference in a new issue