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 state change on save. Exam. {}, Old state: {}, new state: {}",
 | 
				
			||||||
                    exam.externalId,
 | 
					                    exam.externalId,
 | 
				
			||||||
                    oldRecord.getStatus(),
 | 
					                    oldRecord.getStatus(),
 | 
				
			||||||
                    exam.status);
 | 
					                    exam.status);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            final ExamRecord examRecord = new ExamRecord(
 | 
					            UpdateDSL.updateWithMapper(examRecordMapper::update, examRecord)
 | 
				
			||||||
                    exam.id,
 | 
					                .set(supporter).equalTo((exam.supporter != null)
 | 
				
			||||||
                    null, null, null, null,
 | 
					 | 
				
			||||||
                    (exam.supporter != null)
 | 
					 | 
				
			||||||
                        ? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
 | 
					                        ? StringUtils.join(exam.supporter, Constants.LIST_SEPARATOR_CHAR)
 | 
				
			||||||
                            : null,
 | 
					                        : null)
 | 
				
			||||||
                    (exam.type != null)
 | 
					                .set(type).equalTo((exam.type != null)
 | 
				
			||||||
                        ? exam.type.name()
 | 
					                        ? exam.type.name()
 | 
				
			||||||
                            : null,
 | 
					                        : ExamType.UNDEFINED.name())
 | 
				
			||||||
                    null,
 | 
					                .set(quitPassword).equalTo(getEncryptedQuitPassword(exam.quitPassword))
 | 
				
			||||||
                    exam.browserExamKeys,
 | 
					                .set(browserKeys).equalToWhenPresent(exam.browserExamKeys)
 | 
				
			||||||
                    null,
 | 
					                .set(lmsSebRestriction).equalTo(1) // seb restriction (deprecated)
 | 
				
			||||||
                    1, // seb restriction (deprecated)
 | 
					                .set(examTemplateId).equalTo(exam.examTemplateId)
 | 
				
			||||||
                    null, // updating
 | 
					                .set(lastModified).equalTo(Utils.getMillisecondsNow())
 | 
				
			||||||
                    null, // lastUpdate
 | 
					                .set(quizName).equalToWhenPresent(exam.lmsSetupId == null ? exam.name : null)
 | 
				
			||||||
                    null, // active
 | 
					                .set(quizStartTime).equalToWhenPresent(exam.lmsSetupId == null ? exam.startTime : null)
 | 
				
			||||||
                    exam.examTemplateId,
 | 
					                .set(quizEndTime).equalToWhenPresent(exam.lmsSetupId == null ? exam.endTime : null)
 | 
				
			||||||
                    Utils.getMillisecondsNow(),
 | 
					                .where(id, isEqualTo(exam.id))
 | 
				
			||||||
                    exam.lmsSetupId == null ? exam.name : null,
 | 
					                .build()
 | 
				
			||||||
                    exam.lmsSetupId == null ? exam.startTime : null,
 | 
					                .execute();
 | 
				
			||||||
                    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…
	
	Add table
		
		Reference in a new issue