SEBSERV-372 implementation
This commit is contained in:
parent
cfb02143cf
commit
df13b4dff1
11 changed files with 146 additions and 81 deletions
|
@ -108,6 +108,8 @@ public final class API {
|
||||||
|
|
||||||
public static final String EXAM_API_EXAM_SIGNATURE_SALT_HEADER = "SEBExamSalt";
|
public static final String EXAM_API_EXAM_SIGNATURE_SALT_HEADER = "SEBExamSalt";
|
||||||
|
|
||||||
|
public static final String EXAM_API_EXAM_ALT_BEK = "SEBServerBEK";
|
||||||
|
|
||||||
public static final String EXAM_API_USER_SESSION_ID = "seb_user_session_id";
|
public static final String EXAM_API_USER_SESSION_ID = "seb_user_session_id";
|
||||||
|
|
||||||
public static final String EXAM_API_HANDSHAKE_ENDPOINT = "/handshake";
|
public static final String EXAM_API_HANDSHAKE_ENDPOINT = "/handshake";
|
||||||
|
|
|
@ -69,8 +69,10 @@ public final class Exam implements GrantEntity {
|
||||||
public static final String ADDITIONAL_ATTR_NUMERICAL_TRUST_THRESHOLD = "NUMERICAL_TRUST_THRESHOLD";
|
public static final String ADDITIONAL_ATTR_NUMERICAL_TRUST_THRESHOLD = "NUMERICAL_TRUST_THRESHOLD";
|
||||||
/** This attribute name is used to store the signature check encryption certificate is one is used */
|
/** This attribute name is used to store the signature check encryption certificate is one is used */
|
||||||
public static final String ADDITIONAL_ATTR_SIGNATURE_KEY_CERT_ALIAS = "SIGNATURE_KEY_CERT_ALIAS";
|
public static final String ADDITIONAL_ATTR_SIGNATURE_KEY_CERT_ALIAS = "SIGNATURE_KEY_CERT_ALIAS";
|
||||||
|
/** This attribute name is used to store the per exam generated app-signature-key encryption salt */
|
||||||
public static final String ADDITIONAL_ATTR_SIGNATURE_KEY_SALT = "SIGNATURE_KEY_SALT";
|
public static final String ADDITIONAL_ATTR_SIGNATURE_KEY_SALT = "SIGNATURE_KEY_SALT";
|
||||||
|
/** This attribute name is used to store the per Moolde(plugin) exam generated alternative BEK */
|
||||||
|
public static final String ADDITIONAL_ATTR_ALTERNATIVE_SEB_BEK = "ALTERNATIVE_SEB_BEK";
|
||||||
|
|
||||||
public enum ExamStatus {
|
public enum ExamStatus {
|
||||||
UP_COMING,
|
UP_COMING,
|
||||||
|
|
|
@ -209,6 +209,9 @@ public final class Result<T> {
|
||||||
if (result instanceof Result) {
|
if (result instanceof Result) {
|
||||||
throw new IllegalArgumentException("Use flatMap instead!");
|
throw new IllegalArgumentException("Use flatMap instead!");
|
||||||
}
|
}
|
||||||
|
if (result == null) {
|
||||||
|
return Result.ofError(new RuntimeException("Map to null value."));
|
||||||
|
}
|
||||||
return Result.of(result);
|
return Result.of(result);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
return Result.ofError(e);
|
return Result.ofError(e);
|
||||||
|
|
|
@ -26,7 +26,6 @@ import java.util.stream.Collectors;
|
||||||
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.mybatis.dynamic.sql.update.UpdateDSL;
|
import org.mybatis.dynamic.sql.update.UpdateDSL;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||||
|
@ -67,23 +66,17 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
private final ExamRecordDAO examRecordDAO;
|
private final ExamRecordDAO examRecordDAO;
|
||||||
private final ApplicationEventPublisher applicationEventPublisher;
|
private final ApplicationEventPublisher applicationEventPublisher;
|
||||||
private final AdditionalAttributesDAO additionalAttributesDAO;
|
private final AdditionalAttributesDAO additionalAttributesDAO;
|
||||||
private final boolean appSignatureKeyEnabled;
|
|
||||||
private final int defaultNumericalTrustThreshold;
|
|
||||||
|
|
||||||
public ExamDAOImpl(
|
public ExamDAOImpl(
|
||||||
final ExamRecordMapper examRecordMapper,
|
final ExamRecordMapper examRecordMapper,
|
||||||
final ExamRecordDAO examRecordDAO,
|
final ExamRecordDAO examRecordDAO,
|
||||||
final ApplicationEventPublisher applicationEventPublisher,
|
final ApplicationEventPublisher applicationEventPublisher,
|
||||||
final AdditionalAttributesDAO additionalAttributesDAO,
|
final AdditionalAttributesDAO additionalAttributesDAO) {
|
||||||
final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.enabled:false}") boolean appSignatureKeyEnabled,
|
|
||||||
final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.numerical.threshold:2}") int defaultNumericalTrustThreshold) {
|
|
||||||
|
|
||||||
this.examRecordMapper = examRecordMapper;
|
this.examRecordMapper = examRecordMapper;
|
||||||
this.examRecordDAO = examRecordDAO;
|
this.examRecordDAO = examRecordDAO;
|
||||||
this.applicationEventPublisher = applicationEventPublisher;
|
this.applicationEventPublisher = applicationEventPublisher;
|
||||||
this.additionalAttributesDAO = additionalAttributesDAO;
|
this.additionalAttributesDAO = additionalAttributesDAO;
|
||||||
this.appSignatureKeyEnabled = appSignatureKeyEnabled;
|
|
||||||
this.defaultNumericalTrustThreshold = defaultNumericalTrustThreshold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -189,7 +182,6 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
public Result<Exam> createNew(final Exam exam) {
|
public Result<Exam> createNew(final Exam exam) {
|
||||||
return this.examRecordDAO
|
return this.examRecordDAO
|
||||||
.createNew(exam)
|
.createNew(exam)
|
||||||
.map(this::initAdditionalAttributes)
|
|
||||||
.flatMap(rec -> saveAdditionalAttributes(exam, rec))
|
.flatMap(rec -> saveAdditionalAttributes(exam, rec))
|
||||||
.flatMap(this::toDomainModel);
|
.flatMap(this::toDomainModel);
|
||||||
}
|
}
|
||||||
|
@ -765,30 +757,6 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExamRecord initAdditionalAttributes(final ExamRecord rec) {
|
|
||||||
try {
|
|
||||||
final Long examId = rec.getId();
|
|
||||||
this.additionalAttributesDAO.initAdditionalAttribute(
|
|
||||||
EntityType.EXAM,
|
|
||||||
examId,
|
|
||||||
Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED,
|
|
||||||
String.valueOf(this.appSignatureKeyEnabled));
|
|
||||||
this.additionalAttributesDAO.initAdditionalAttribute(
|
|
||||||
EntityType.EXAM,
|
|
||||||
examId,
|
|
||||||
Exam.ADDITIONAL_ATTR_NUMERICAL_TRUST_THRESHOLD,
|
|
||||||
String.valueOf(this.defaultNumericalTrustThreshold));
|
|
||||||
final CharSequence salt = KeyGenerators.string().generateKey();
|
|
||||||
this.additionalAttributesDAO.initAdditionalAttribute(
|
|
||||||
EntityType.EXAM,
|
|
||||||
examId,
|
|
||||||
Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_SALT, salt.toString());
|
|
||||||
} catch (final Exception e) {
|
|
||||||
log.error("Failed to init additional attributes: ", e);
|
|
||||||
}
|
|
||||||
return rec;
|
|
||||||
}
|
|
||||||
|
|
||||||
private QuizData saveAdditionalQuizAttributes(final Long examId, final QuizData quizData) {
|
private QuizData saveAdditionalQuizAttributes(final Long examId, final QuizData quizData) {
|
||||||
final Map<String, String> additionalAttributes = new HashMap<>(quizData.getAdditionalAttributes());
|
final Map<String, String> additionalAttributes = new HashMap<>(quizData.getAdditionalAttributes());
|
||||||
if (StringUtils.isNotBlank(quizData.description)) {
|
if (StringUtils.isNotBlank(quizData.description)) {
|
||||||
|
|
|
@ -38,6 +38,12 @@ public interface ExamAdminService {
|
||||||
* @return Result refer to the domain object or to an error when happened */
|
* @return Result refer to the domain object or to an error when happened */
|
||||||
Result<Exam> examForPK(Long examId);
|
Result<Exam> examForPK(Long examId);
|
||||||
|
|
||||||
|
/** Initializes initial additional attributes for a yet created exam.
|
||||||
|
*
|
||||||
|
* @param exam The exam that has been created
|
||||||
|
* @return The exam with the initial additional attributes */
|
||||||
|
Result<Exam> initAdditionalAttributes(final Exam exam);
|
||||||
|
|
||||||
/** Saves additional attributes for the exam that are specific to a type of LMS
|
/** Saves additional attributes for the exam that are specific to a type of LMS
|
||||||
*
|
*
|
||||||
* @param exam The Exam to add the LMS specific attributes
|
* @param exam The Exam to add the LMS specific attributes
|
||||||
|
|
|
@ -8,14 +8,19 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl;
|
||||||
|
|
||||||
|
import java.security.MessageDigest;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
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.bouncycastle.util.encoders.Hex;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@ -35,6 +40,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
|
||||||
|
@ -59,6 +65,8 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
private final ConfigurationNodeDAO configurationNodeDAO;
|
private final ConfigurationNodeDAO configurationNodeDAO;
|
||||||
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
private final ExamConfigurationMapDAO examConfigurationMapDAO;
|
||||||
private final LmsAPIService lmsAPIService;
|
private final LmsAPIService lmsAPIService;
|
||||||
|
private final boolean appSignatureKeyEnabled;
|
||||||
|
private final int defaultNumericalTrustThreshold;
|
||||||
|
|
||||||
protected ExamAdminServiceImpl(
|
protected ExamAdminServiceImpl(
|
||||||
final ExamDAO examDAO,
|
final ExamDAO examDAO,
|
||||||
|
@ -66,7 +74,9 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
final AdditionalAttributesDAO additionalAttributesDAO,
|
final AdditionalAttributesDAO additionalAttributesDAO,
|
||||||
final ConfigurationNodeDAO configurationNodeDAO,
|
final ConfigurationNodeDAO configurationNodeDAO,
|
||||||
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
final ExamConfigurationMapDAO examConfigurationMapDAO,
|
||||||
final LmsAPIService lmsAPIService) {
|
final LmsAPIService lmsAPIService,
|
||||||
|
final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.enabled:false}") boolean appSignatureKeyEnabled,
|
||||||
|
final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.numerical.threshold:2}") int defaultNumericalTrustThreshold) {
|
||||||
|
|
||||||
this.examDAO = examDAO;
|
this.examDAO = examDAO;
|
||||||
this.proctoringServiceSettingsService = proctoringServiceSettingsService;
|
this.proctoringServiceSettingsService = proctoringServiceSettingsService;
|
||||||
|
@ -74,6 +84,8 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
this.configurationNodeDAO = configurationNodeDAO;
|
this.configurationNodeDAO = configurationNodeDAO;
|
||||||
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
this.examConfigurationMapDAO = examConfigurationMapDAO;
|
||||||
this.lmsAPIService = lmsAPIService;
|
this.lmsAPIService = lmsAPIService;
|
||||||
|
this.appSignatureKeyEnabled = appSignatureKeyEnabled;
|
||||||
|
this.defaultNumericalTrustThreshold = defaultNumericalTrustThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -81,6 +93,34 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
return this.examDAO.byPK(examId);
|
return this.examDAO.byPK(examId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Exam> initAdditionalAttributes(final Exam exam) {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
final Long examId = exam.getId();
|
||||||
|
|
||||||
|
// initialize App-Signature-Key feature attributes
|
||||||
|
this.additionalAttributesDAO.initAdditionalAttribute(
|
||||||
|
EntityType.EXAM,
|
||||||
|
examId,
|
||||||
|
Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED,
|
||||||
|
String.valueOf(this.appSignatureKeyEnabled));
|
||||||
|
|
||||||
|
this.additionalAttributesDAO.initAdditionalAttribute(
|
||||||
|
EntityType.EXAM,
|
||||||
|
examId,
|
||||||
|
Exam.ADDITIONAL_ATTR_NUMERICAL_TRUST_THRESHOLD,
|
||||||
|
String.valueOf(this.defaultNumericalTrustThreshold));
|
||||||
|
|
||||||
|
this.additionalAttributesDAO.initAdditionalAttribute(
|
||||||
|
EntityType.EXAM,
|
||||||
|
examId,
|
||||||
|
Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_SALT,
|
||||||
|
KeyGenerators.string().generateKey().toString());
|
||||||
|
|
||||||
|
return exam;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Exam> saveSecurityKeySettings(
|
public Result<Exam> saveSecurityKeySettings(
|
||||||
final Long institutionId,
|
final Long institutionId,
|
||||||
|
@ -148,7 +188,7 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Exam> saveLMSAttributes(final Exam exam) {
|
public Result<Exam> saveLMSAttributes(final Exam exam) {
|
||||||
return saveAdditionalAttributesForMoodleExams(exam);
|
return initAdditionalAttributesForMoodleExams(exam);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -208,7 +248,7 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
.getExamProctoringService(settings.serverType));
|
.getExamProctoringService(settings.serverType));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result<Exam> saveAdditionalAttributesForMoodleExams(final Exam exam) {
|
private Result<Exam> initAdditionalAttributesForMoodleExams(final Exam exam) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
final LmsAPITemplate lmsTemplate = this.lmsAPIService
|
final LmsAPITemplate lmsTemplate = this.lmsAPIService
|
||||||
.getLmsAPITemplate(exam.lmsSetupId)
|
.getLmsAPITemplate(exam.lmsSetupId)
|
||||||
|
@ -221,7 +261,26 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
exam.id,
|
exam.id,
|
||||||
QuizData.QUIZ_ATTR_NAME,
|
QuizData.QUIZ_ATTR_NAME,
|
||||||
quizData.name))
|
quizData.name))
|
||||||
.getOrThrow();
|
.onError(error -> log.error("Failed to create additional moodle quiz name attribute: ", error));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lmsTemplate.lmsSetup().lmsType == LmsType.MOODLE_PLUGIN) {
|
||||||
|
// Save additional Browser Exam Key for Moodle plugin integration SEBSERV-372
|
||||||
|
try {
|
||||||
|
|
||||||
|
final String moodleBEKUUID = UUID.randomUUID().toString();
|
||||||
|
final MessageDigest hasher = MessageDigest.getInstance(Constants.SHA_256);
|
||||||
|
hasher.update(Utils.toByteArray(moodleBEKUUID));
|
||||||
|
final String moodleBEK = Hex.toHexString(hasher.digest());
|
||||||
|
|
||||||
|
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||||
|
EntityType.EXAM,
|
||||||
|
exam.id,
|
||||||
|
Exam.ADDITIONAL_ATTR_ALTERNATIVE_SEB_BEK,
|
||||||
|
moodleBEK).getOrThrow();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Failed to create additional moodle SEB BEK attribute: ", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return exam;
|
return exam;
|
||||||
|
|
|
@ -343,7 +343,7 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
|
||||||
|
|
||||||
private String createSignatureHash(final CharSequence signature) {
|
private String createSignatureHash(final CharSequence signature) {
|
||||||
try {
|
try {
|
||||||
final MessageDigest hasher = MessageDigest.getInstance("SHA-256");
|
final MessageDigest hasher = MessageDigest.getInstance(Constants.SHA_256);
|
||||||
hasher.update(Utils.toByteArray(signature));
|
hasher.update(Utils.toByteArray(signature));
|
||||||
final String signatureHash = Hex.toHexString(hasher.digest());
|
final String signatureHash = Hex.toHexString(hasher.digest());
|
||||||
return signatureHash;
|
return signatureHash;
|
||||||
|
|
|
@ -63,6 +63,11 @@ public interface ExamSessionService {
|
||||||
* @return the underling LmsAPIService */
|
* @return the underling LmsAPIService */
|
||||||
LmsAPIService getLmsAPIService();
|
LmsAPIService getLmsAPIService();
|
||||||
|
|
||||||
|
/** Get the app-signature-key for the given exam.
|
||||||
|
* Ensures that if no app-signature-key exists already for the exam, a new on is created and stored
|
||||||
|
*
|
||||||
|
* @param examId The exam identifier
|
||||||
|
* @return App-Signature-Key value for the exam */
|
||||||
Result<String> getAppSignatureKeySalt(Long examId);
|
Result<String> getAppSignatureKeySalt(Long examId);
|
||||||
|
|
||||||
/** Use this to check the consistency of a running Exam.
|
/** Use this to check the consistency of a running Exam.
|
||||||
|
|
|
@ -173,7 +173,6 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
||||||
this.clientIndicatorFactory.initializeDistributedCaches(clientConnection);
|
this.clientIndicatorFactory.initializeDistributedCaches(clientConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// flash connection token cache for exam if available
|
|
||||||
if (examId != null) {
|
if (examId != null) {
|
||||||
this.clientConnectionDAO.evictConnectionTokenCache(examId);
|
this.clientConnectionDAO.evictConnectionTokenCache(examId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,20 +145,15 @@ public class ExamAPI_V1_Controller {
|
||||||
.filter(this::checkConsistency)
|
.filter(this::checkConsistency)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
final Exam exam = this.examSessionService
|
final Exam exam = this.examSessionService
|
||||||
.getExamDAO()
|
.getExamDAO()
|
||||||
.byPK(examId)
|
.byPK(examId)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
result = Arrays.asList(createRunningExamInfo(exam));
|
result = Arrays.asList(createRunningExamInfo(exam));
|
||||||
|
processASKSalt(response, clientConnection);
|
||||||
this.examSessionService
|
processAlternativeBEK(response, clientConnection.examId);
|
||||||
.getAppSignatureKeySalt(clientConnection.examId)
|
|
||||||
.onSuccess(salt -> response.setHeader(API.EXAM_API_EXAM_SIGNATURE_SALT_HEADER, salt))
|
|
||||||
.onError(error -> log.error(
|
|
||||||
"Failed to get security key salt for connection: {}",
|
|
||||||
clientConnection,
|
|
||||||
error));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.isEmpty()) {
|
if (result.isEmpty()) {
|
||||||
|
@ -209,27 +204,23 @@ public class ExamAPI_V1_Controller {
|
||||||
final String remoteAddr = this.getClientAddress(request);
|
final String remoteAddr = this.getClientAddress(request);
|
||||||
final Long institutionId = getInstitutionId(principal);
|
final Long institutionId = getInstitutionId(principal);
|
||||||
|
|
||||||
final ClientConnection clientConnection = this.sebClientConnectionService.updateClientConnection(
|
final ClientConnection clientConnection = this.sebClientConnectionService
|
||||||
connectionToken,
|
.updateClientConnection(
|
||||||
institutionId,
|
connectionToken,
|
||||||
examId,
|
institutionId,
|
||||||
remoteAddr,
|
examId,
|
||||||
sebVersion,
|
remoteAddr,
|
||||||
sebOSName,
|
sebVersion,
|
||||||
sebMachinName,
|
sebOSName,
|
||||||
userSessionId,
|
sebMachinName,
|
||||||
clientId,
|
userSessionId,
|
||||||
browserSignatureKey)
|
clientId,
|
||||||
|
browserSignatureKey)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
if (clientConnection.examId != null) {
|
if (clientConnection.examId != null) {
|
||||||
this.examSessionService
|
processASKSalt(response, clientConnection);
|
||||||
.getAppSignatureKeySalt(clientConnection.examId)
|
processAlternativeBEK(response, clientConnection.examId);
|
||||||
.onSuccess(salt -> response.setHeader(API.EXAM_API_EXAM_SIGNATURE_SALT_HEADER, salt))
|
|
||||||
.onError(error -> log.error(
|
|
||||||
"Failed to get security key salt for connection: {}",
|
|
||||||
clientConnection,
|
|
||||||
error));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
this.executor);
|
this.executor);
|
||||||
|
@ -251,7 +242,8 @@ public class ExamAPI_V1_Controller {
|
||||||
required = false) final String browserSignatureKey,
|
required = false) final String browserSignatureKey,
|
||||||
@RequestParam(name = API.EXAM_API_PARAM_CLIENT_ID, required = false) final String clientId,
|
@RequestParam(name = API.EXAM_API_PARAM_CLIENT_ID, required = false) final String clientId,
|
||||||
final Principal principal,
|
final Principal principal,
|
||||||
final HttpServletRequest request) {
|
final HttpServletRequest request,
|
||||||
|
final HttpServletResponse response) {
|
||||||
|
|
||||||
return CompletableFuture.runAsync(
|
return CompletableFuture.runAsync(
|
||||||
() -> {
|
() -> {
|
||||||
|
@ -259,18 +251,23 @@ public class ExamAPI_V1_Controller {
|
||||||
final String remoteAddr = this.getClientAddress(request);
|
final String remoteAddr = this.getClientAddress(request);
|
||||||
final Long institutionId = getInstitutionId(principal);
|
final Long institutionId = getInstitutionId(principal);
|
||||||
|
|
||||||
this.sebClientConnectionService.establishClientConnection(
|
final ClientConnection clientConnection = this.sebClientConnectionService
|
||||||
connectionToken,
|
.establishClientConnection(
|
||||||
institutionId,
|
connectionToken,
|
||||||
examId,
|
institutionId,
|
||||||
remoteAddr,
|
examId,
|
||||||
sebVersion,
|
remoteAddr,
|
||||||
sebOSName,
|
sebVersion,
|
||||||
sebMachinName,
|
sebOSName,
|
||||||
userSessionId,
|
sebMachinName,
|
||||||
clientId,
|
userSessionId,
|
||||||
browserSignatureKey)
|
clientId,
|
||||||
|
browserSignatureKey)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
|
if (clientConnection.examId != null) {
|
||||||
|
processAlternativeBEK(response, clientConnection.examId);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
this.executor);
|
this.executor);
|
||||||
}
|
}
|
||||||
|
@ -467,4 +464,23 @@ public class ExamAPI_V1_Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processASKSalt(final HttpServletResponse response, final ClientConnection clientConnection) {
|
||||||
|
this.examSessionService
|
||||||
|
.getAppSignatureKeySalt(clientConnection.examId)
|
||||||
|
.onSuccess(salt -> response.setHeader(API.EXAM_API_EXAM_SIGNATURE_SALT_HEADER, salt))
|
||||||
|
.onError(error -> log.error(
|
||||||
|
"Failed to get security key salt for connection: {}",
|
||||||
|
clientConnection,
|
||||||
|
error));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processAlternativeBEK(final HttpServletResponse response, final Long examId) {
|
||||||
|
if (examId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.examSessionService.getRunningExam(examId)
|
||||||
|
.map(exam -> exam.getAdditionalAttribute(Exam.ADDITIONAL_ATTR_ALTERNATIVE_SEB_BEK))
|
||||||
|
.onSuccess(bek -> response.setHeader(API.EXAM_API_EXAM_ALT_BEK, bek));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -512,8 +512,13 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
protected Result<Exam> notifyCreated(final Exam entity) {
|
protected Result<Exam> notifyCreated(final Exam entity) {
|
||||||
final List<APIMessage> errors = new ArrayList<>();
|
final List<APIMessage> errors = new ArrayList<>();
|
||||||
|
|
||||||
this.examTemplateService
|
this.examAdminService
|
||||||
.addDefinedIndicators(entity)
|
.initAdditionalAttributes(entity)
|
||||||
|
.onErrorDo(error -> {
|
||||||
|
errors.add(ErrorMessage.EXAM_IMPORT_ERROR_AUTO_ATTRIBUTES.of(error));
|
||||||
|
return entity;
|
||||||
|
})
|
||||||
|
.flatMap(this.examTemplateService::addDefinedIndicators)
|
||||||
.onErrorDo(error -> {
|
.onErrorDo(error -> {
|
||||||
errors.add(ErrorMessage.EXAM_IMPORT_ERROR_AUTO_INDICATOR.of(error));
|
errors.add(ErrorMessage.EXAM_IMPORT_ERROR_AUTO_INDICATOR.of(error));
|
||||||
return entity;
|
return entity;
|
||||||
|
@ -549,7 +554,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
API.PARAM_MODEL_ID + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + entity.getModelId()));
|
API.PARAM_MODEL_ID + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR + entity.getModelId()));
|
||||||
throw new APIMessageException(errors);
|
throw new APIMessageException(errors);
|
||||||
} else {
|
} else {
|
||||||
return Result.of(entity);
|
return this.examDAO.byPK(entity.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue