SEBSERV-376 back-end implementation
This commit is contained in:
parent
a6f7c501ca
commit
504c2a0843
16 changed files with 543 additions and 17 deletions
|
@ -77,6 +77,7 @@ public final class Constants {
|
||||||
public static final Character LIST_SEPARATOR_CHAR = COMMA;
|
public static final Character LIST_SEPARATOR_CHAR = COMMA;
|
||||||
public static final Character COMPLEX_VALUE_SEPARATOR = COLON;
|
public static final Character COMPLEX_VALUE_SEPARATOR = COLON;
|
||||||
public static final Character HASH_TAG = '#';
|
public static final Character HASH_TAG = '#';
|
||||||
|
public static final Character DOT = '.';
|
||||||
|
|
||||||
public static final String NULL = "null";
|
public static final String NULL = "null";
|
||||||
public static final String PERCENTAGE_STRING = Constants.PERCENTAGE.toString();
|
public static final String PERCENTAGE_STRING = Constants.PERCENTAGE.toString();
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.gbl.model.exam;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
|
|
||||||
|
public class AllowedSEBVersion {
|
||||||
|
|
||||||
|
public static final String OS_WINDOWS_IDENTIFIER = "Win";
|
||||||
|
public static final String OS_MAC_IDENTIFIER = "Mac";
|
||||||
|
public static final String OS_IOS_IDENTIFIER = "iOS";
|
||||||
|
public static final String ALIANCE_EDITION_IDENTIFIER = "AE";
|
||||||
|
public static final String MINIMAL_IDENTIFIER = "min";
|
||||||
|
|
||||||
|
public final String wholeVersionString;
|
||||||
|
public final String osTypeString;
|
||||||
|
public final int major;
|
||||||
|
public final int minor;
|
||||||
|
public final int patch;
|
||||||
|
public final boolean alianceEdition;
|
||||||
|
public final boolean minimal;
|
||||||
|
public final boolean isValidFormat;
|
||||||
|
|
||||||
|
public AllowedSEBVersion(final String wholeVersionString) {
|
||||||
|
this.wholeVersionString = wholeVersionString;
|
||||||
|
|
||||||
|
boolean valid = true;
|
||||||
|
final String[] split = StringUtils.split(wholeVersionString, Constants.DOT);
|
||||||
|
if (OS_WINDOWS_IDENTIFIER.equalsIgnoreCase(split[0])) {
|
||||||
|
this.osTypeString = OS_WINDOWS_IDENTIFIER;
|
||||||
|
} else if (OS_MAC_IDENTIFIER.equalsIgnoreCase(split[0])) {
|
||||||
|
this.osTypeString = OS_MAC_IDENTIFIER;
|
||||||
|
} else if (OS_IOS_IDENTIFIER.equalsIgnoreCase(split[0])) {
|
||||||
|
this.osTypeString = OS_IOS_IDENTIFIER;
|
||||||
|
} else {
|
||||||
|
this.osTypeString = null;
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int num = -1;
|
||||||
|
try {
|
||||||
|
num = Integer.valueOf(split[1]);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
this.major = num;
|
||||||
|
|
||||||
|
try {
|
||||||
|
num = Integer.valueOf(split[2]);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
this.minor = num;
|
||||||
|
try {
|
||||||
|
num = Integer.valueOf(split[3]);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
this.patch = num;
|
||||||
|
|
||||||
|
if (split.length > 4 && ALIANCE_EDITION_IDENTIFIER.equalsIgnoreCase(split[4])) {
|
||||||
|
this.alianceEdition = true;
|
||||||
|
if (split.length > 5 && MINIMAL_IDENTIFIER.equalsIgnoreCase(split[5])) {
|
||||||
|
this.minimal = true;
|
||||||
|
} else {
|
||||||
|
this.minimal = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.alianceEdition = false;
|
||||||
|
if (split.length > 4 && MINIMAL_IDENTIFIER.equalsIgnoreCase(split[4])) {
|
||||||
|
this.minimal = true;
|
||||||
|
} else {
|
||||||
|
this.minimal = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isValidFormat = valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean match(final ClientVersion clientVersion) {
|
||||||
|
if (Objects.equals(this.osTypeString, clientVersion.osTypeString)) {
|
||||||
|
if (this.minimal) {
|
||||||
|
// check greater or equals minimum version
|
||||||
|
return this.major <= clientVersion.major ||
|
||||||
|
this.minor <= clientVersion.minor ||
|
||||||
|
this.patch <= clientVersion.patch;
|
||||||
|
} else {
|
||||||
|
// check exact match
|
||||||
|
return this.major == clientVersion.major &&
|
||||||
|
this.minor == clientVersion.minor &&
|
||||||
|
this.patch == clientVersion.patch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ClientVersion {
|
||||||
|
|
||||||
|
public String osTypeString;
|
||||||
|
public final int major;
|
||||||
|
public final int minor;
|
||||||
|
public final int patch;
|
||||||
|
|
||||||
|
public ClientVersion(final String osTypeString, final int major, final int minor, final int patch) {
|
||||||
|
this.osTypeString = osTypeString;
|
||||||
|
this.major = major;
|
||||||
|
this.minor = minor;
|
||||||
|
this.patch = patch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
@ -73,6 +74,8 @@ public final class Exam implements GrantEntity {
|
||||||
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 */
|
/** 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";
|
||||||
|
/** Comma separated String value that defines allowed SEB version from linked Exam Configuration */
|
||||||
|
public static final String ADDITIONAL_ATTR_ALLOWED_SEB_VERSIONS = "ALLOWED_SEB_VERSIONS";
|
||||||
|
|
||||||
public enum ExamStatus {
|
public enum ExamStatus {
|
||||||
UP_COMING,
|
UP_COMING,
|
||||||
|
@ -149,6 +152,11 @@ public final class Exam implements GrantEntity {
|
||||||
@JsonProperty(ATTR_ADDITIONAL_ATTRIBUTES)
|
@JsonProperty(ATTR_ADDITIONAL_ATTRIBUTES)
|
||||||
public final Map<String, String> additionalAttributes;
|
public final Map<String, String> additionalAttributes;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
public final boolean checkASK;
|
||||||
|
@JsonIgnore
|
||||||
|
public final List<AllowedSEBVersion> allowedSEBVersions;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public Exam(
|
public Exam(
|
||||||
@JsonProperty(EXAM.ATTR_ID) final Long id,
|
@JsonProperty(EXAM.ATTR_ID) final Long id,
|
||||||
|
@ -193,7 +201,11 @@ public final class Exam implements GrantEntity {
|
||||||
? Collections.unmodifiableCollection(supporter)
|
? Collections.unmodifiableCollection(supporter)
|
||||||
: Collections.emptyList();
|
: Collections.emptyList();
|
||||||
|
|
||||||
this.additionalAttributes = additionalAttributes;
|
this.additionalAttributes = Utils.immutableMapOf(additionalAttributes);
|
||||||
|
|
||||||
|
this.checkASK = BooleanUtils
|
||||||
|
.toBoolean(this.additionalAttributes.get(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED));
|
||||||
|
this.allowedSEBVersions = initAllowedSEBVersions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Exam(final String modelId, final QuizData quizData, final POSTMapper mapper) {
|
public Exam(final String modelId, final QuizData quizData, final POSTMapper mapper) {
|
||||||
|
@ -225,6 +237,9 @@ public final class Exam implements GrantEntity {
|
||||||
this.lastModified = null;
|
this.lastModified = null;
|
||||||
this.additionalAttributes = Utils.immutableMapOf(additionalAttributes);
|
this.additionalAttributes = Utils.immutableMapOf(additionalAttributes);
|
||||||
|
|
||||||
|
this.checkASK = BooleanUtils
|
||||||
|
.toBoolean(this.additionalAttributes.get(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED));
|
||||||
|
this.allowedSEBVersions = initAllowedSEBVersions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Exam(final QuizData quizData) {
|
public Exam(final QuizData quizData) {
|
||||||
|
@ -251,6 +266,25 @@ public final class Exam implements GrantEntity {
|
||||||
this.examTemplateId = null;
|
this.examTemplateId = null;
|
||||||
this.lastModified = null;
|
this.lastModified = null;
|
||||||
this.additionalAttributes = null;
|
this.additionalAttributes = null;
|
||||||
|
this.checkASK = false;
|
||||||
|
this.allowedSEBVersions = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<AllowedSEBVersion> initAllowedSEBVersions() {
|
||||||
|
if (this.additionalAttributes.containsKey(ADDITIONAL_ATTR_ALLOWED_SEB_VERSIONS)) {
|
||||||
|
final String asvString = this.additionalAttributes.get(Exam.ADDITIONAL_ATTR_ALLOWED_SEB_VERSIONS);
|
||||||
|
final String[] split = StringUtils.split(asvString, Constants.LIST_SEPARATOR);
|
||||||
|
final List<AllowedSEBVersion> result = new ArrayList<>();
|
||||||
|
for (int i = 0; i < split.length; i++) {
|
||||||
|
final AllowedSEBVersion allowedSEBVersion = new AllowedSEBVersion(split[i]);
|
||||||
|
if (allowedSEBVersion.isValidFormat) {
|
||||||
|
result.add(allowedSEBVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -13,6 +13,8 @@ import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
|
@ -216,11 +218,11 @@ public final class ClientConnection implements GrantEntity {
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
this.info = new StringBuilder()
|
this.info = new StringBuilder()
|
||||||
.append((clientAddress != null) ? clientAddress : Constants.EMPTY_NOTE)
|
.append(getSEBInfo(seb_version))
|
||||||
.append(Constants.LIST_SEPARATOR)
|
.append(Constants.LIST_SEPARATOR)
|
||||||
.append((seb_os_name != null) ? seb_os_name : Constants.EMPTY_NOTE)
|
.append(getOSInfo(seb_os_name))
|
||||||
.append(Constants.LIST_SEPARATOR)
|
.append(Constants.LIST_SEPARATOR)
|
||||||
.append((seb_version != null) ? seb_version : Constants.EMPTY_NOTE)
|
.append((clientAddress != null) ? "IP: " + clientAddress : Constants.EMPTY_NOTE)
|
||||||
.toString();
|
.toString();
|
||||||
|
|
||||||
this.securityCheckGranted = securityCheckGranted;
|
this.securityCheckGranted = securityCheckGranted;
|
||||||
|
@ -439,4 +441,16 @@ public final class ClientConnection implements GrantEntity {
|
||||||
return connection -> states.contains(connection.status);
|
return connection -> states.contains(connection.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getSEBInfo(final String seb_version) {
|
||||||
|
return (seb_version != null) ? "SEBV: " + seb_version : Constants.EMPTY_NOTE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getOSInfo(final String seb_os_name) {
|
||||||
|
if (seb_os_name != null) {
|
||||||
|
final String[] split = StringUtils.split(seb_os_name, Constants.LIST_SEPARATOR);
|
||||||
|
return "OSV: " + split[0];
|
||||||
|
}
|
||||||
|
return Constants.EMPTY_NOTE;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,11 @@ public interface ClientMonitoringDataView {
|
||||||
return notificationFlag != null && (notificationFlag & FLAG_GRANT_DENIED) > 0;
|
return notificationFlag != null && (notificationFlag & FLAG_GRANT_DENIED) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default boolean isSEBVersionDenied() {
|
||||||
|
final Integer notificationFlag = notificationFlag();
|
||||||
|
return notificationFlag != null && (notificationFlag & FLAG_INVALID_SEB_VERSION) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
default boolean isPendingNotification() {
|
default boolean isPendingNotification() {
|
||||||
final Integer notificationFlag = notificationFlag();
|
final Integer notificationFlag = notificationFlag();
|
||||||
return notificationFlag != null && (notificationFlag & FLAG_PENDING_NOTIFICATION) > 0;
|
return notificationFlag != null && (notificationFlag & FLAG_PENDING_NOTIFICATION) > 0;
|
||||||
|
|
|
@ -171,15 +171,27 @@ public interface ClientConnectionDAO extends
|
||||||
* @return Result refer to a collection of client connection records or to an error when happened */
|
* @return Result refer to a collection of client connection records or to an error when happened */
|
||||||
Result<Collection<ClientConnectionRecord>> getsecurityKeyConnectionRecords(Long examId);
|
Result<Collection<ClientConnectionRecord>> getsecurityKeyConnectionRecords(Long examId);
|
||||||
|
|
||||||
/** Get all client connection records that don't have an security access grant yet
|
/** Get all client connection records that don't have a security access grant yet
|
||||||
* and for specific exam.
|
* and for specific exam.
|
||||||
*
|
*
|
||||||
* @param examId The exam identifier
|
* @param examId The exam identifier
|
||||||
* @return Result refer to client connection records to the an error when happened */
|
* @return Result refer to client connection records to the an error when happened */
|
||||||
Result<Collection<ClientConnectionRecord>> getAllActiveNotGranted(Long examId);
|
Result<Collection<ClientConnectionRecord>> getAllActiveNotGranted(Long examId);
|
||||||
|
|
||||||
|
/** Count all known and matching ASK hashes for a given exam.
|
||||||
|
*
|
||||||
|
* @param examId The exam identifier
|
||||||
|
* @param signatureHash The signature hash the count
|
||||||
|
* @return Result refer to the signature hash count or to an result when happened */
|
||||||
Result<Long> countSignatureHashes(Long examId, String signatureHash);
|
Result<Long> countSignatureHashes(Long examId, String signatureHash);
|
||||||
|
|
||||||
|
/** Get all client connection records that don't have a SEB client version check yet
|
||||||
|
* and for specific exam.
|
||||||
|
*
|
||||||
|
* @param examId The exam identifier
|
||||||
|
* @return Result refer to client connection records to the an error when happened */
|
||||||
|
Result<Collection<ClientConnectionRecord>> getAllActiveNoSEBVersionCheck(Long examId);
|
||||||
|
|
||||||
/** Get all client connection identifiers for an exam.
|
/** Get all client connection identifiers for an exam.
|
||||||
*
|
*
|
||||||
* @param examId the exam identifier
|
* @param examId the exam identifier
|
||||||
|
@ -188,9 +200,16 @@ public interface ClientConnectionDAO extends
|
||||||
|
|
||||||
/** Saves the given security check status for specified client connection id
|
/** Saves the given security check status for specified client connection id
|
||||||
*
|
*
|
||||||
* @param id the client connection identifier (PK)
|
* @param connectionId the client connection identifier (PK)
|
||||||
* @param checkStatus The status to save
|
* @param checkStatus The status to save
|
||||||
* @return Result refer to the given check status or to an error when happened */
|
* @return Result refer to the given check status or to an error when happened */
|
||||||
Result<Boolean> saveSecurityCheckStatus(Long id, Boolean checkStatus);
|
Result<Boolean> saveSecurityCheckStatus(Long connectionId, Boolean checkStatus);
|
||||||
|
|
||||||
|
/** Saves the given SEB version check status for specified client connection id
|
||||||
|
*
|
||||||
|
* @param connectionId the client connection identifier (PK)
|
||||||
|
* @param checkStatus The status to save
|
||||||
|
* @return Result refer to the given check status or to an error when happened */
|
||||||
|
Result<Boolean> saveSEBClientVersionCheckStatus(Long connectionId, Boolean checkStatus);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -446,6 +446,19 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
.onError(TransactionHandler::rollback);
|
.onError(TransactionHandler::rollback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Result<Boolean> saveSEBClientVersionCheckStatus(final Long connectionId, final Boolean checkStatus) {
|
||||||
|
return Result.tryCatch(() -> {
|
||||||
|
this.clientConnectionRecordMapper.updateByPrimaryKeySelective(new ClientConnectionRecord(
|
||||||
|
connectionId,
|
||||||
|
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
|
||||||
|
null, null, Utils.toByte(checkStatus)));
|
||||||
|
return checkStatus;
|
||||||
|
})
|
||||||
|
.onError(TransactionHandler::rollback);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public Result<Void> assignToProctoringRoom(
|
public Result<Void> assignToProctoringRoom(
|
||||||
|
@ -838,6 +851,25 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
.execute());
|
.execute());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public Result<Collection<ClientConnectionRecord>> getAllActiveNoSEBVersionCheck(final Long examId) {
|
||||||
|
return Result.tryCatch(() -> this.clientConnectionRecordMapper
|
||||||
|
.selectByExample()
|
||||||
|
.where(
|
||||||
|
ClientConnectionRecordDynamicSqlSupport.status,
|
||||||
|
SqlBuilder.isIn(ClientConnection.SECURE_CHECK_STATES))
|
||||||
|
.and(
|
||||||
|
ClientConnectionRecordDynamicSqlSupport.examId,
|
||||||
|
SqlBuilder.isEqualTo(examId))
|
||||||
|
.and(
|
||||||
|
ClientConnectionRecordDynamicSqlSupport.clientVersionGranted,
|
||||||
|
SqlBuilder.isNull())
|
||||||
|
.and(ClientConnectionRecordDynamicSqlSupport.ask, SqlBuilder.isNotNull())
|
||||||
|
.build()
|
||||||
|
.execute());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Result<Collection<Long>> getAllConnectionIdsForExam(final Long examId) {
|
public Result<Collection<Long>> getAllConnectionIdsForExam(final Long examId) {
|
||||||
|
|
|
@ -21,8 +21,22 @@ public interface ExamConfigurationValueService {
|
||||||
* @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(Long examId, String configAttributeName);
|
String getMappedDefaultConfigAttributeValue(Long examId, String configAttributeName);
|
||||||
|
|
||||||
|
/** Get the quitPassword SEB Setting from the Exam Configuration that is applied to the given exam.
|
||||||
|
*
|
||||||
|
* @param examId Exam identifier
|
||||||
|
* @return the vlaue of the quitPassword SEB Setting */
|
||||||
String getQuitSecret(Long examId);
|
String getQuitSecret(Long examId);
|
||||||
|
|
||||||
|
/** Get the quitLink SEB Setting from the Exam Configuration that is applied to the given exam.
|
||||||
|
*
|
||||||
|
* @param examId Exam identifier
|
||||||
|
* @return the value of the quitLink SEB Setting */
|
||||||
String getQuitLink(Long examId);
|
String getQuitLink(Long examId);
|
||||||
|
|
||||||
|
/** Get the allowedSEBVersions SEB Setting from the Exam Configuration that is applied to the given exam.
|
||||||
|
*
|
||||||
|
* @param examId Exam identifier
|
||||||
|
* @return the value of the allowedSEBVersions SEB Setting */
|
||||||
|
String getAllowedSEBVersion(Long examId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,4 +124,18 @@ public class ExamConfigurationValueServiceImpl implements ExamConfigurationValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAllowedSEBVersion(final Long examId) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
return getMappedDefaultConfigAttributeValue(
|
||||||
|
examId,
|
||||||
|
CONFIG_ATTR_NAME_QUIT_LINK);
|
||||||
|
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Failed to get SEB restriction with quit link: ", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.AllowedSEBVersion;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
|
@ -61,8 +62,7 @@ public class SEBVersionValidator implements ConfigurationValueValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidSEBVersionMarker(final String versionMarker) {
|
private boolean isValidSEBVersionMarker(final String versionMarker) {
|
||||||
// TODO Auto-generated method stub
|
return new AllowedSEBVersion(versionMarker).isValidFormat;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.webservice.servicelayer.session;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.AllowedSEBVersion;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord;
|
||||||
|
|
||||||
|
public interface SEBClientVersionService {
|
||||||
|
|
||||||
|
/** Use this to check if a given SEB Version from SEB client connections are valid
|
||||||
|
*
|
||||||
|
* @param clientOSName The client OS name sent by SEB client
|
||||||
|
* @param clientVersion The SEB version sent by SEB client
|
||||||
|
* @param allowedSEBVersions List of allowed SEB version conditions given from the Exam Configuration.
|
||||||
|
* @return True if the given SEB Version matches one of the defined AllowedSEBVersion conditions */
|
||||||
|
boolean isAllowedVersion(
|
||||||
|
String clientOSName,
|
||||||
|
String clientVersion,
|
||||||
|
List<AllowedSEBVersion> allowedSEBVersions);
|
||||||
|
|
||||||
|
void checkVersionAndUpdateClientConnection(
|
||||||
|
ClientConnectionRecord record,
|
||||||
|
List<AllowedSEBVersion> allowedSEBVersions);
|
||||||
|
|
||||||
|
}
|
|
@ -45,6 +45,7 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
|
||||||
private final PendingNotificationIndication pendingNotificationIndication;
|
private final PendingNotificationIndication pendingNotificationIndication;
|
||||||
|
|
||||||
private final Boolean grantDenied;
|
private final Boolean grantDenied;
|
||||||
|
private final Boolean sebVersionDenied;
|
||||||
|
|
||||||
public ClientConnectionDataInternal(
|
public ClientConnectionDataInternal(
|
||||||
final ClientConnection clientConnection,
|
final ClientConnection clientConnection,
|
||||||
|
@ -76,6 +77,12 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
|
||||||
} else {
|
} else {
|
||||||
this.grantDenied = !clientConnection.securityCheckGranted;
|
this.grantDenied = !clientConnection.securityCheckGranted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clientConnection.clientVersionGranted == null) {
|
||||||
|
this.sebVersionDenied = null;
|
||||||
|
} else {
|
||||||
|
this.sebVersionDenied = !clientConnection.clientVersionGranted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void notifyPing(final long timestamp, final int pingNumber) {
|
public final void notifyPing(final long timestamp, final int pingNumber) {
|
||||||
|
@ -175,6 +182,11 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
|
||||||
return BooleanUtils.isTrue(ClientConnectionDataInternal.this.grantDenied);
|
return BooleanUtils.isTrue(ClientConnectionDataInternal.this.grantDenied);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean isSEBVersionDenied() {
|
||||||
|
return BooleanUtils.isTrue(ClientConnectionDataInternal.this.sebVersionDenied);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** This is a static monitoring connection data wrapper/holder */
|
/** This is a static monitoring connection data wrapper/holder */
|
||||||
|
|
|
@ -108,6 +108,7 @@ public class ExamSessionControlTask implements DisposableBean {
|
||||||
controlExamLMSUpdate();
|
controlExamLMSUpdate();
|
||||||
controlExamState(updateId);
|
controlExamState(updateId);
|
||||||
this.examDAO.releaseAgedLocks();
|
this.examDAO.releaseAgedLocks();
|
||||||
|
this.sebClientSessionService.cleanupInstructions();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(
|
@Scheduled(
|
||||||
|
@ -127,7 +128,6 @@ public class ExamSessionControlTask implements DisposableBean {
|
||||||
|
|
||||||
this.sebClientSessionService.updatePingEvents();
|
this.sebClientSessionService.updatePingEvents();
|
||||||
this.sebClientSessionService.updateASKGrants();
|
this.sebClientSessionService.updateASKGrants();
|
||||||
this.sebClientSessionService.cleanupInstructions();
|
|
||||||
this.examProcotringRoomService.updateProctoringCollectingRooms();
|
this.examProcotringRoomService.updateProctoringCollectingRooms();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,15 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
|
||||||
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;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
import ch.ethz.seb.sebserver.gbl.model.exam.AllowedSEBVersion;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||||
|
@ -33,6 +33,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.EventHandlingStrate
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientSessionService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientSessionService;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientVersionService;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
@Service
|
@Service
|
||||||
|
@ -49,6 +50,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
|
||||||
private final ClientIndicatorFactory clientIndicatorFactory;
|
private final ClientIndicatorFactory clientIndicatorFactory;
|
||||||
private final InternalClientConnectionDataFactory internalClientConnectionDataFactory;
|
private final InternalClientConnectionDataFactory internalClientConnectionDataFactory;
|
||||||
private final SecurityKeyService securityKeyService;
|
private final SecurityKeyService securityKeyService;
|
||||||
|
private final SEBClientVersionService sebClientVersionService;
|
||||||
|
|
||||||
public SEBClientSessionServiceImpl(
|
public SEBClientSessionServiceImpl(
|
||||||
final ClientConnectionDAO clientConnectionDAO,
|
final ClientConnectionDAO clientConnectionDAO,
|
||||||
|
@ -57,7 +59,8 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
|
||||||
final SEBClientInstructionService sebInstructionService,
|
final SEBClientInstructionService sebInstructionService,
|
||||||
final ClientIndicatorFactory clientIndicatorFactory,
|
final ClientIndicatorFactory clientIndicatorFactory,
|
||||||
final InternalClientConnectionDataFactory internalClientConnectionDataFactory,
|
final InternalClientConnectionDataFactory internalClientConnectionDataFactory,
|
||||||
final SecurityKeyService securityKeyService) {
|
final SecurityKeyService securityKeyService,
|
||||||
|
final SEBClientVersionService sebClientVersionService) {
|
||||||
|
|
||||||
this.clientConnectionDAO = clientConnectionDAO;
|
this.clientConnectionDAO = clientConnectionDAO;
|
||||||
this.examSessionService = examSessionService;
|
this.examSessionService = examSessionService;
|
||||||
|
@ -67,6 +70,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
|
||||||
this.clientIndicatorFactory = clientIndicatorFactory;
|
this.clientIndicatorFactory = clientIndicatorFactory;
|
||||||
this.internalClientConnectionDataFactory = internalClientConnectionDataFactory;
|
this.internalClientConnectionDataFactory = internalClientConnectionDataFactory;
|
||||||
this.securityKeyService = securityKeyService;
|
this.securityKeyService = securityKeyService;
|
||||||
|
this.sebClientVersionService = sebClientVersionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -97,7 +101,7 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
|
||||||
this.examSessionService
|
this.examSessionService
|
||||||
.getExamDAO()
|
.getExamDAO()
|
||||||
.allRunningExamIds()
|
.allRunningExamIds()
|
||||||
.onSuccess(ids -> ids.stream().forEach(examId -> updateASKGrant(examId)))
|
.onSuccess(ids -> ids.stream().forEach(examId -> updateGrants(examId)))
|
||||||
.onError(error -> log.error("Unexpected error while trying to updateASKGrants: ", error));
|
.onError(error -> log.error("Unexpected error while trying to updateASKGrants: ", error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,11 +205,23 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateGrants(final Long examId) {
|
||||||
|
try {
|
||||||
|
updateASKGrant(examId);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Failed to update ASK grant for exam: {}", examId, e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
updateAllowedSEBVersionGrant(examId);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Failed to update SEB client version grant for exam: {}", examId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void updateASKGrant(final Long examId) {
|
private void updateASKGrant(final Long examId) {
|
||||||
if (this.examSessionService
|
if (this.examSessionService
|
||||||
.getRunningExam(examId)
|
.getRunningExam(examId)
|
||||||
.map(exam -> exam.getAdditionalAttribute(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED))
|
.map(exam -> exam.checkASK)
|
||||||
.map(BooleanUtils::toBoolean)
|
|
||||||
.getOr(true)) {
|
.getOr(true)) {
|
||||||
|
|
||||||
this.clientConnectionDAO
|
this.clientConnectionDAO
|
||||||
|
@ -218,4 +234,22 @@ public class SEBClientSessionServiceImpl implements SEBClientSessionService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateAllowedSEBVersionGrant(final Long examId) {
|
||||||
|
final List<AllowedSEBVersion> allowedSEBVersions = this.examSessionService
|
||||||
|
.getRunningExam(examId)
|
||||||
|
.map(exam -> exam.allowedSEBVersions)
|
||||||
|
.getOr(Collections.emptyList());
|
||||||
|
|
||||||
|
if (allowedSEBVersions != null && !allowedSEBVersions.isEmpty()) {
|
||||||
|
this.clientConnectionDAO
|
||||||
|
.getAllActiveNoSEBVersionCheck(examId)
|
||||||
|
.onError(error -> log.error(
|
||||||
|
"Failed to get none SEB version checked active client connections: ",
|
||||||
|
error))
|
||||||
|
.getOr(Collections.emptyList())
|
||||||
|
.forEach(cc -> this.sebClientVersionService.checkVersionAndUpdateClientConnection(
|
||||||
|
cc,
|
||||||
|
allowedSEBVersions));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 ETH Zürich, Educational Development and Technology (LET)
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.AllowedSEBVersion;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.exam.AllowedSEBVersion.ClientVersion;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientVersionService;
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
@Service
|
||||||
|
@WebServiceProfile
|
||||||
|
public class SEBClientVersionServiceImpl implements SEBClientVersionService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(SEBClientVersionServiceImpl.class);
|
||||||
|
|
||||||
|
private final ClientConnectionDAO clientConnectionDAO;
|
||||||
|
private final ExamSessionCacheService examSessionCacheService;
|
||||||
|
|
||||||
|
public final Set<String> knownWindowsOSTags;
|
||||||
|
public final Set<String> knownMacOSTags;
|
||||||
|
public final Set<String> knownIOSTags;
|
||||||
|
public final Set<String> knownRestrictedVersions;
|
||||||
|
|
||||||
|
public SEBClientVersionServiceImpl(
|
||||||
|
final ClientConnectionDAO clientConnectionDAO,
|
||||||
|
final ExamSessionCacheService examSessionCacheService,
|
||||||
|
@Value("${ebserver.webservice.config.knownWindowsOSTags:Win,Windows}") final String knownWindowsOSTags,
|
||||||
|
@Value("${ebserver.webservice.config.knownMacOSTags:macOS}") final String knownMacOSTags,
|
||||||
|
@Value("${ebserver.webservice.config.knownIOSTags:iOS,iPad,iPadOS}") final String knownIOSTags,
|
||||||
|
@Value("${ebserver.webservice.config.knownRestrictedVersions:BETA,rc,1.0.0.0}") final String knownRestrictedVersions) {
|
||||||
|
|
||||||
|
this.clientConnectionDAO = clientConnectionDAO;
|
||||||
|
this.examSessionCacheService = examSessionCacheService;
|
||||||
|
this.knownWindowsOSTags = new HashSet<>(Arrays.asList(StringUtils.split(
|
||||||
|
knownWindowsOSTags,
|
||||||
|
Constants.LIST_SEPARATOR)));
|
||||||
|
this.knownMacOSTags = new HashSet<>(Arrays.asList(StringUtils.split(
|
||||||
|
knownMacOSTags,
|
||||||
|
Constants.LIST_SEPARATOR)));
|
||||||
|
this.knownIOSTags = new HashSet<>(Arrays.asList(StringUtils.split(
|
||||||
|
knownIOSTags,
|
||||||
|
Constants.LIST_SEPARATOR)));
|
||||||
|
this.knownRestrictedVersions = new HashSet<>(Arrays.asList(StringUtils.split(
|
||||||
|
knownRestrictedVersions,
|
||||||
|
Constants.LIST_SEPARATOR)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowedVersion(
|
||||||
|
final String clientOSName,
|
||||||
|
final String clientVersion,
|
||||||
|
final List<AllowedSEBVersion> allowedSEBVersions) {
|
||||||
|
|
||||||
|
// first check if this is a known restricted version
|
||||||
|
if (this.knownRestrictedVersions.stream().filter(clientVersion::contains).findFirst().isPresent()) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Found default restricted SEB client version: {}", clientVersion);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String osType = verifyOSType(clientOSName, clientVersion);
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(osType)) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("No SEB client OS type tag found in : {} {}", clientOSName, clientVersion);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final String[] versionSplit = StringUtils.split(clientVersion, Constants.SPACE);
|
||||||
|
final String versioNumber = versionSplit[0];
|
||||||
|
final String[] versionNumberSplit = StringUtils.split(versioNumber, Constants.DOT);
|
||||||
|
|
||||||
|
final int major = extractVersionNumber(versionNumberSplit[0]);
|
||||||
|
final int minor = extractVersionNumber(versionNumberSplit[1]);
|
||||||
|
final int patch = extractVersionNumber(versionNumberSplit[2]);
|
||||||
|
|
||||||
|
final ClientVersion version = new ClientVersion(osType, major, minor, patch);
|
||||||
|
return allowedSEBVersions
|
||||||
|
.stream()
|
||||||
|
.filter(v -> v.match(version))
|
||||||
|
.findFirst()
|
||||||
|
.isPresent();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.warn("Unexpected error while trying to parse SEB version number in: {} {}", clientOSName,
|
||||||
|
clientVersion);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkVersionAndUpdateClientConnection(
|
||||||
|
final ClientConnectionRecord record,
|
||||||
|
final List<AllowedSEBVersion> allowedSEBVersions) {
|
||||||
|
|
||||||
|
if (isAllowedVersion(record.getClientOsName(), record.getClientVersion(), allowedSEBVersions)) {
|
||||||
|
saveSecurityCheckState(record, true);
|
||||||
|
} else {
|
||||||
|
saveSecurityCheckState(record, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int extractVersionNumber(final String versionNumPart) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(versionNumPart);
|
||||||
|
} catch (final NumberFormatException nfe) {
|
||||||
|
return Integer.parseInt(String.valueOf(versionNumPart.charAt(0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String verifyOSType(final String clientOSName, final String clientVersion) {
|
||||||
|
final char c = clientVersion.charAt(0);
|
||||||
|
final String osVersionText = (c >= 'A' && c <= 'Z') ? clientVersion : clientOSName;
|
||||||
|
if (StringUtils.isNotBlank(osVersionText)) {
|
||||||
|
if (this.knownWindowsOSTags.stream().filter(osVersionText::contains).findFirst().isPresent()) {
|
||||||
|
return AllowedSEBVersion.OS_WINDOWS_IDENTIFIER;
|
||||||
|
}
|
||||||
|
if (this.knownMacOSTags.stream().filter(osVersionText::contains).findFirst().isPresent()) {
|
||||||
|
return AllowedSEBVersion.OS_MAC_IDENTIFIER;
|
||||||
|
}
|
||||||
|
if (this.knownIOSTags.stream().filter(osVersionText::contains).findFirst().isPresent()) {
|
||||||
|
return AllowedSEBVersion.OS_IOS_IDENTIFIER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveSecurityCheckState(final ClientConnectionRecord record, final Boolean checkStatus) {
|
||||||
|
this.clientConnectionDAO
|
||||||
|
.saveSEBClientVersionCheckStatus(record.getId(), checkStatus)
|
||||||
|
.onError(error -> log.error("Failed to save ClientConnection grant: ",
|
||||||
|
error))
|
||||||
|
.onSuccess(c -> this.examSessionCacheService.evictClientConnection(record.getConnectionToken()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,11 +64,13 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.Authorization
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||||
|
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
|
||||||
|
@ -93,6 +95,8 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
private final SEBRestrictionService sebRestrictionService;
|
private final SEBRestrictionService sebRestrictionService;
|
||||||
private final SecurityKeyService securityKeyService;
|
private final SecurityKeyService securityKeyService;
|
||||||
private final ExamProctoringRoomService examProctoringRoomService;
|
private final ExamProctoringRoomService examProctoringRoomService;
|
||||||
|
private final ExamConfigurationValueService examConfigurationValueService;
|
||||||
|
private final AdditionalAttributesDAO additionalAttributesDAO;
|
||||||
|
|
||||||
public ExamAdministrationController(
|
public ExamAdministrationController(
|
||||||
final AuthorizationService authorization,
|
final AuthorizationService authorization,
|
||||||
|
@ -108,7 +112,9 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
final ExamSessionService examSessionService,
|
final ExamSessionService examSessionService,
|
||||||
final SEBRestrictionService sebRestrictionService,
|
final SEBRestrictionService sebRestrictionService,
|
||||||
final SecurityKeyService securityKeyService,
|
final SecurityKeyService securityKeyService,
|
||||||
final ExamProctoringRoomService examProctoringRoomService) {
|
final ExamProctoringRoomService examProctoringRoomService,
|
||||||
|
final ExamConfigurationValueService examConfigurationValueService,
|
||||||
|
final AdditionalAttributesDAO additionalAttributesDAO) {
|
||||||
|
|
||||||
super(authorization,
|
super(authorization,
|
||||||
bulkActionService,
|
bulkActionService,
|
||||||
|
@ -126,6 +132,8 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
this.sebRestrictionService = sebRestrictionService;
|
this.sebRestrictionService = sebRestrictionService;
|
||||||
this.securityKeyService = securityKeyService;
|
this.securityKeyService = securityKeyService;
|
||||||
this.examProctoringRoomService = examProctoringRoomService;
|
this.examProctoringRoomService = examProctoringRoomService;
|
||||||
|
this.examConfigurationValueService = examConfigurationValueService;
|
||||||
|
this.additionalAttributesDAO = additionalAttributesDAO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -599,6 +607,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
@Override
|
@Override
|
||||||
protected Result<Exam> notifySaved(final Exam entity) {
|
protected Result<Exam> notifySaved(final Exam entity) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
this.saveAdditionalExamConfigAttributes(entity);
|
||||||
this.examSessionService.flushCache(entity);
|
this.examSessionService.flushCache(entity);
|
||||||
return entity;
|
return entity;
|
||||||
});
|
});
|
||||||
|
@ -702,6 +711,29 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveAdditionalExamConfigAttributes(final Exam entity) {
|
||||||
|
try {
|
||||||
|
final String allowedSEBVersion = this.examConfigurationValueService
|
||||||
|
.getAllowedSEBVersion(entity.id);
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(allowedSEBVersion)) {
|
||||||
|
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||||
|
EntityType.EXAM,
|
||||||
|
entity.id, Exam.ADDITIONAL_ATTR_ALLOWED_SEB_VERSIONS,
|
||||||
|
allowedSEBVersion)
|
||||||
|
.getOrThrow();
|
||||||
|
} else {
|
||||||
|
this.additionalAttributesDAO.delete(
|
||||||
|
EntityType.EXAM,
|
||||||
|
entity.id, Exam.ADDITIONAL_ATTR_ALLOWED_SEB_VERSIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Unexpected error while trying to save additional Exam Configuration settings for exam: {}",
|
||||||
|
entity, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Function<Collection<Exam>, List<Exam>> pageSort(final String sort) {
|
static Function<Collection<Exam>, List<Exam>> pageSort(final String sort) {
|
||||||
|
|
||||||
final String sortBy = PageSortOrder.decode(sort);
|
final String sortBy = PageSortOrder.decode(sort);
|
||||||
|
|
Loading…
Reference in a new issue