SEBSERV-495 implementation and fix special chars in password

This commit is contained in:
anhefti 2023-12-21 13:24:39 +01:00
parent b837163c79
commit c301016d87
19 changed files with 424 additions and 55 deletions

View file

@ -192,7 +192,9 @@ public final class API {
public static final String CONFIGURATION_SEB_SETTINGS_DOWNLOAD_PATH_SEGMENT = "/downloadSettings";
public static final String CONFIGURATION_IMPORT_PATH_SEGMENT = "/import";
public static final String IMPORT_PASSWORD_ATTR_NAME = "importFilePassword";
public static final String QUIT_PASSWORD_ATTR_NAME = "quitPassword";
public static final String IMPORT_FILE_ATTR_NAME = "importFile";
public static final String CONFIGURATION_SET_QUIT_PWD_PATH_SEGMENT = "/quitpwd";
public static final String TEMPLATE_ATTRIBUTE_ENDPOINT = "/template-attribute";
public static final String TEMPLATE_ATTRIBUTE_RESET_VALUES = "/reset";

View file

@ -11,16 +11,19 @@ package ch.ethz.seb.sebserver.gui.content.configs;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.*;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import ch.ethz.seb.sebserver.gui.form.FieldBuilder;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.*;
import ch.ethz.seb.sebserver.gui.widget.PasswordConfirmInput;
import ch.ethz.seb.sebserver.gui.widget.PasswordInput;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
@ -53,10 +56,6 @@ import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNodeNames;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportExamConfigOnExistingConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportNewExamConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfigHistory;
import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@ -77,6 +76,14 @@ public class SEBExamConfigImportPopup {
new LocTextKey("sebserver.examconfig.action.import.missing-password"));
private static final LocTextKey MESSAGE_SAVE_INTEGRITY_VIOLATION =
new LocTextKey("sebserver.examconfig.action.saveToHistory.integrity-violation");
private static final LocTextKey TITLE_QUIT_PASSWORD =
new LocTextKey("sebserver.examconfig.action.import.quitpwd.title");
private static final LocTextKey LABEL_QUIT_PASSWORD =
new LocTextKey("sebserver.clientconfig.form.hashedQuitPassword");
private static final LocTextKey MESSAGE_QUIT_PASSWORD_REMOVED =
new LocTextKey("sebserver.examconfig.action.import.quitpwd.removed");
private static final LocTextKey MESSAGE_QUIT_PASSWORD_SET =
new LocTextKey("sebserver.examconfig.action.import.quitpwd.set");
private final static String AUTO_PUBLISH_FLAG = "AUTO_PUBLISH_FLAG";
@ -178,15 +185,18 @@ public class SEBExamConfigImportPopup {
final Result<Configuration> importResult = restCall.call();
if (!importResult.hasError()) {
context.publishInfo(SEBExamConfigForm.FORM_IMPORT_CONFIRM_TEXT_KEY);
final Configuration configuration = importResult.get();
// Auto publish?
if (!newConfig && BooleanUtils.toBoolean(form.getFieldValue(AUTO_PUBLISH_FLAG))) {
this.pageService.getRestService()
.getBuilder(SaveExamConfigHistory.class)
.withURIVariable(API.PARAM_MODEL_ID, importResult.get().getModelId())
.withURIVariable(API.PARAM_MODEL_ID, configuration.getModelId())
.call()
.onError(error -> notifyErrorOnSave(error, context));
}
handleQuitPassword(configuration, context);
} else {
handleImportError(formHandle, importResult);
}
@ -207,6 +217,81 @@ public class SEBExamConfigImportPopup {
}
}
private void handleQuitPassword(final Configuration configuration, final PageContext context) {
try {
final ConfigurationValue configurationValue = this.pageService.getRestService()
.getBuilder(GetConfigurationValues.class)
.withQueryParam(
ConfigurationValue.FILTER_ATTR_CONFIGURATION_ID,
configuration.getModelId())
.call()
.getOrThrow()
.stream()
.filter(v -> v.attributeId.intValue() == 4)
.findFirst()
.orElse(null);
final boolean hashedSet = configurationValue != null &&
StringUtils.isNotBlank(configurationValue.getValue());
final ModalInputDialog<PasswordConfirmInput> dialog = new ModalInputDialog<>(
context.getShell(),
this.pageService.getWidgetFactory());
final ModalInputDialogComposer<PasswordConfirmInput> contentComposer = comp -> {
this.pageService.getWidgetFactory().labelLocalized(
comp,
hashedSet ? MESSAGE_QUIT_PASSWORD_REMOVED : MESSAGE_QUIT_PASSWORD_SET,
true);
final PasswordConfirmInput passwordInput = new PasswordConfirmInput(
comp,
this.pageService.getWidgetFactory(),
this.pageService.getCryptor(),
LABEL_QUIT_PASSWORD,
LABEL_QUIT_PASSWORD);
return () -> passwordInput;
};
final String configNodeId = String.valueOf(configuration.configurationNodeId);
final Predicate<PasswordConfirmInput> callback = input -> {
if (input.hasError()) {
return false;
}
final CharSequence value = input.getValue();
if (value != null) {
this.pageService.getRestService()
.getBuilder(ResetImportQuitPassword.class)
.withURIVariable(API.PARAM_MODEL_ID, configNodeId)
.withFormParam(API.QUIT_PASSWORD_ATTR_NAME, String.valueOf(value))
.call()
.onError(context::notifyUnexpectedError);
} else {
deleteQuitPassword(context, configNodeId);
}
return true;
};
final Runnable cancel = () -> deleteQuitPassword(context, configNodeId);
dialog.open(TITLE_QUIT_PASSWORD, callback, cancel, contentComposer);
} catch (final Exception e) {
log.error("Failed to handle quit password for import: ", e);
}
}
private void deleteQuitPassword(final PageContext context, final String configNodeId) {
this.pageService.getRestService()
.getBuilder(ResetImportQuitPassword.class)
.withURIVariable(API.PARAM_MODEL_ID, configNodeId)
.call()
.onError(context::notifyUnexpectedError);
}
private void handleImportError(
final FormHandle<ConfigurationNode> formHandle,
final Result<Configuration> configuration) {
@ -285,9 +370,7 @@ public class SEBExamConfigImportPopup {
.call()
.getOrThrow()
.stream()
.filter(n -> n.name.equals(fieldValue))
.findFirst()
.isPresent()) {
.anyMatch(n -> n.name.equals(fieldValue))) {
form.setFieldError(
Domain.CONFIGURATION_NODE.ATTR_NAME,
@ -304,6 +387,8 @@ public class SEBExamConfigImportPopup {
return true;
}
private final class ImportFormContext implements ModalInputDialogComposer<FormHandle<ConfigurationNode>> {
private final PageService pageService;

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gui.service.examconfig.impl;
import java.util.Collection;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
@ -104,7 +105,7 @@ public abstract class AbstractInputField<T extends Control> implements InputFiel
return;
}
this.errorLabel.setVisible(false);
this.errorLabel.setText("");
this.errorLabel.setText(StringUtils.EMPTY);
}

View file

@ -18,6 +18,7 @@ import java.util.function.Function;
import java.util.function.Supplier;
import ch.ethz.seb.sebserver.gbl.FeatureService;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.graphics.Point;
@ -73,6 +74,8 @@ public interface PageService {
Logger log = LoggerFactory.getLogger(PageService.class);
Cryptor getCryptor();
FeatureService getFeatureService();
/** Get the WidgetFactory service

View file

@ -13,6 +13,7 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import ch.ethz.seb.sebserver.gui.widget.PasswordInput;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Rectangle;
@ -157,6 +158,7 @@ public class ModalInputDialog<T> extends Dialog {
finishUp(this.shell);
}
public void open(
final LocTextKey title,
final Predicate<T> okCallback,

View file

@ -111,9 +111,14 @@ public class PageServiceImpl implements PageService {
this.featureService = featureService;
}
@Override
public Cryptor getCryptor() {
return this.cryptor;
}
@Override
public FeatureService getFeatureService() {
return featureService;
return this.featureService;
}
@Override

View file

@ -430,6 +430,7 @@ public abstract class RestCall<T> {
+ this.queryParams
+ ", uriVariables=" + this.uriVariables + "]";
}
}
public static final class TypeKey<T> {

View file

@ -0,0 +1,32 @@
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import com.fasterxml.jackson.core.type.TypeReference;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
@Lazy
@Component
@GuiProfile
public class ResetImportQuitPassword extends RestCall<ConfigurationNode> {
public ResetImportQuitPassword() {
super(new TypeKey<>(
CallType.UNDEFINED,
EntityType.CONFIGURATION_NODE,
new TypeReference<ConfigurationNode>() {
}),
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.CONFIGURATION_NODE_ENDPOINT +
API.MODEL_ID_VAR_PATH_SEGMENT +
API.CONFIGURATION_SET_QUIT_PWD_PATH_SEGMENT);
}
}

View file

@ -0,0 +1,106 @@
package ch.ethz.seb.sebserver.gui.widget;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
public class PasswordConfirmInput extends Composite {
private static final LocTextKey VAL_CONFIRM_PWD_TEXT_KEY =
new LocTextKey("sebserver.examconfig.props.validation.password.confirm");
final WidgetFactory widgetFactory;
final Cryptor cryptor;
private final PasswordInput password;
private final PasswordInput confirm;
private final Label errorLabel;
public PasswordConfirmInput(
final Composite parent,
final WidgetFactory widgetFactory,
final Cryptor cryptor,
final LocTextKey ariaLabel,
final LocTextKey testLabel) {
super(parent, SWT.NONE);
this.widgetFactory = widgetFactory;
this.cryptor = cryptor;
final GridLayout gridLayout = new GridLayout(1, false);
gridLayout.horizontalSpacing = 0;
gridLayout.verticalSpacing = 10;
gridLayout.marginHeight = 0;
gridLayout.marginWidth = 10;
this.setLayout(gridLayout);
this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
this.password = new PasswordInput(parent, widgetFactory, ariaLabel, testLabel);
this.confirm = new PasswordInput(parent, widgetFactory, ariaLabel, testLabel);
this.errorLabel = new Label(parent, SWT.NONE);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
errorLabel.setLayoutData(gridData);
errorLabel.setVisible(false);
errorLabel.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.ERROR.key);
final Listener valueChangeEventListener = event -> checkError();
this.password.addListener(SWT.FocusOut, valueChangeEventListener);
this.password.addListener(SWT.Traverse, valueChangeEventListener);
this.confirm.addListener(SWT.FocusOut, valueChangeEventListener);
this.confirm.addListener(SWT.Traverse, valueChangeEventListener);
}
public void setValue(final CharSequence value) {
if (StringUtils.isBlank(value)) {
this.password.setValue(null);
this.confirm.setValue(null);
} else {
final CharSequence val = cryptor.decrypt(value).getOr(value);
this.password.setValue(val);
this.confirm.setValue(val);
}
}
public CharSequence getValue() {
final CharSequence value = password.getValue();
if (StringUtils.isNotBlank(value)) {
return cryptor.encrypt(value).getOr(value);
} else {
return null;
}
}
public boolean hasError() {
return checkError();
}
public void clearError() {
this.errorLabel.setVisible(false);
this.errorLabel.setText(StringUtils.EMPTY);
}
private boolean checkError() {
clearError();
final CharSequence pwd = this.password.getValue();
final CharSequence confirm = this.confirm.getValue();
if (pwd == null && confirm == null) {
return false;
}
if (!pwd.equals(confirm)) {
errorLabel.setText(widgetFactory.getI18nSupport().getText(VAL_CONFIRM_PWD_TEXT_KEY));
errorLabel.setVisible(true);
return true;
}
return false;
}
}

View file

@ -143,7 +143,7 @@ public class PasswordInput extends Composite {
public void setValue(final CharSequence value) {
if (this.passwordInputField != null) {
this.passwordInputField.setText(value != null
? Utils.escapeHTML_XML_EcmaScript(value.toString())
? value.toString()
: StringUtils.EMPTY);
if (StringUtils.endsWith(value, Constants.IMPORTED_PASSWORD_MARKER)) {
this.visibilityButton.setEnabled(false);

View file

@ -36,7 +36,7 @@ public interface ConfigurationDAO extends EntityDAO<Configuration, Configuration
}
/** Saves the current follow-up Configuration of the ConfigurationNode of given id
* as a point in history and creates new new follow-up Configuration.
* as a point in history and creates new follow-up Configuration.
*
* @param configurationNodeId the identifier of the ConfigurationNode to create a new history entry from current
* follow-up
@ -50,9 +50,9 @@ public interface ConfigurationDAO extends EntityDAO<Configuration, Configuration
Result<Configuration> undo(Long configurationNodeId);
/** Restores the attribute values to the default values that have been set for the specified configuration
* on initialization. This are the base default values if the configuration has no template or the default
* on initialization. These are the base default values if the configuration has no template or the default
* values from the template if there is one assigned to the configuration.
*
* <p>
* In fact. this just gets the initial configuration values and reset the current values with that one
*
* @param configurationNodeId the ConfigurationNode identifier
@ -60,9 +60,9 @@ public interface ConfigurationDAO extends EntityDAO<Configuration, Configuration
Result<Configuration> restoreToDefaultValues(final Long configurationNodeId);
/** Restores the attribute values to the default values that have been set for the specified configuration
* on initialization. This are the base default values if the configuration has no template or the default
* on initialization. These are the base default values if the configuration has no template or the default
* values from the template if there is one assigned to the configuration.
*
* <p>
* In fact. this just gets the initial configuration values and reset the current values with that one
*
* @param configuration the Configuration that defines the ConfigurationNode identifier
@ -107,7 +107,7 @@ public interface ConfigurationDAO extends EntityDAO<Configuration, Configuration
/** Use this to get the follow-up configuration identifer for a specified configuration node.
*
* @param configurationNode ConfigurationNode to get the current follow-up configuration from
* @param configNodeId ConfigurationNode to get the current follow-up configuration from
* @return the current follow-up configuration identifier */
Result<Long> getFollowupConfigurationId(Long configNodeId);

View file

@ -78,4 +78,15 @@ public interface ConfigurationValueDAO extends EntityDAO<ConfigurationValue, Con
* @return the String value of the SEB setting attribute */
Result<String> getConfigAttributeValue(Long configId, Long attrId);
/** This applies the ignore SEB Service policy as described in Issue SEBWIN-464 on the given configuration
*
* @param configurationId The configuration identifier*/
void applyIgnoreSEBService(Long institutionId, Long configurationId);
/** Saves the given hashed quit password as value for the given configuration
*
* @param configurationId The configuration identifier
* @param pwd The hashed quit password
* @return Result refer to void or to an error when happened*/
Result<Void> saveQuitPassword(Long configurationId, String pwd);
}

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl;
import static ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValueRecordDynamicSqlSupport.*;
import static java.lang.reflect.Array.set;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo;
import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
@ -27,9 +29,12 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.*;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlBuilder;
import org.mybatis.dynamic.sql.update.MyBatis3UpdateModelAdapter;
import org.mybatis.dynamic.sql.update.UpdateDSL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
@ -52,11 +57,6 @@ import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValu
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationAttributeRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationValueRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigInitService;
@Lazy
@ -156,6 +156,37 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
});
}
private static final String KEY_SEB_SERVICE_POLICY = "sebServicePolicy";
private static final String KEY_ATTR_1 = "enableWindowsUpdate";
private static final String KEY_ATTR_2 = "enableChromeNotifications";
private static final String KEY_ATTR_3 = "allowScreenSharing";
@Override
public void applyIgnoreSEBService(final Long institutionId, final Long configurationId) {
try {
final String val = this.getConfigAttributeValue(configurationId, 318L).getOrThrow();
final boolean ignoreSEBService = BooleanUtils.toBoolean(val);
if (ignoreSEBService) {
// set default values sebServicePolicy
this.setDefaultValues(institutionId, configurationId, 300L)
.onError(error -> log.warn("Failed to set defaultValue on IgnoreSEBService for sebServicePolicy"));
// set default values enableWindowsUpdate
this.setDefaultValues(institutionId, configurationId, 321L)
.onError(error -> log.warn("Failed to set defaultValue on IgnoreSEBService for sebServicePolicy"));
// set default values enableChromeNotifications
this.setDefaultValues(institutionId, configurationId, 322L)
.onError(error -> log.warn("Failed to set defaultValue on IgnoreSEBService for sebServicePolicy"));
// set default values allowScreenSharing
this.setDefaultValues(institutionId, configurationId, 303L)
.onError(error -> log.warn("Failed to set defaultValue on IgnoreSEBService for sebServicePolicy"));
}
} catch (final Exception e) {
log.error("Failed to apply Ignore SEB Service to configuration: {}", configurationId, e);
}
}
@Override
@Transactional(readOnly = true)
public Result<Collection<ConfigurationValue>> allOf(final Set<Long> pks) {
@ -276,6 +307,37 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO {
.onError(TransactionHandler::rollback);
}
@Override
@Transactional
public Result<Void> saveQuitPassword(final Long configId, final String pwd) {
return Result.tryCatch(() -> {
final Long hashedQuitPasswordId = configurationAttributeRecordMapper.selectIdsByExample()
.where(ConfigurationAttributeRecordDynamicSqlSupport.name, isEqualTo("hashedQuitPassword"))
.build()
.execute()
.get(0);
UpdateDSL<MyBatis3UpdateModelAdapter<Integer>> dsl = UpdateDSL.updateWithMapper(
configurationValueRecordMapper::update, configurationValueRecord);
if (StringUtils.isNotBlank(pwd)) {
dsl = dsl.set(value).equalTo(pwd);
} else {
dsl = dsl.set(value).equalToNull();
}
final Integer execute = dsl.where(configurationId, isEqualTo(configId))
.and(configurationAttributeId, isEqualTo(hashedQuitPasswordId))
.build()
.execute();
if (execute == null || execute != 1) {
throw new NoResourceFoundException(EntityType.CONFIGURATION_VALUE, "Failed to force save");
}
});
}
@Override
@Transactional
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {

View file

@ -158,4 +158,10 @@ public interface ExamConfigService {
* @return Result refer to the given ConfigurationNode or to an error if the check has failed */
Result<ConfigurationNode> checkSaveConsistency(ConfigurationNode configurationNode);
/** Sets or resets an imported (hashed) quiz password
*
* @param node the ConfigurationNode
* @param quitPassword the quit password to reset (if null or empty, no quit password shall be set)
* @return Result refer to the origin ConfigurationNode or to an error when happened*/
Result<ConfigurationNode> setQuitPassword(ConfigurationNode node, String quitPassword);
}

View file

@ -20,6 +20,7 @@ import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.*;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@ -41,10 +42,6 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
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.sebconfig.ConfigurationFormat;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationValueValidator;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
@ -64,6 +61,7 @@ public class ExamConfigServiceImpl implements ExamConfigService {
private final ExamConfigIO examConfigIO;
private final ConfigurationNodeDAO configurationNodeDAO;
private final ConfigurationAttributeDAO configurationAttributeDAO;
private final ConfigurationValueDAO configurationValueDAO;
private final ExamConfigurationMapDAO examConfigurationMapDAO;
private final Collection<ConfigurationValueValidator> validators;
private final ClientCredentialService clientCredentialService;
@ -75,6 +73,7 @@ public class ExamConfigServiceImpl implements ExamConfigService {
final ExamConfigIO examConfigIO,
final ConfigurationNodeDAO configurationNodeDAO,
final ConfigurationAttributeDAO configurationAttributeDAO,
final ConfigurationValueDAO configurationValueDAO,
final ExamConfigurationMapDAO examConfigurationMapDAO,
final Collection<ConfigurationValueValidator> validators,
final ClientCredentialService clientCredentialService,
@ -85,6 +84,7 @@ public class ExamConfigServiceImpl implements ExamConfigService {
this.examConfigIO = examConfigIO;
this.configurationNodeDAO = configurationNodeDAO;
this.configurationAttributeDAO = configurationAttributeDAO;
this.configurationValueDAO = configurationValueDAO;
this.examConfigurationMapDAO = examConfigurationMapDAO;
this.validators = validators;
this.clientCredentialService = clientCredentialService;
@ -384,7 +384,7 @@ public class ExamConfigServiceImpl implements ExamConfigService {
if (streamDecrypted != null) {
final Exception exception = streamDecrypted.get();
if (exception != null && exception instanceof APIMessageException) {
if (exception instanceof APIMessageException) {
throw exception;
}
}
@ -478,6 +478,34 @@ public class ExamConfigServiceImpl implements ExamConfigService {
});
}
@Override
public Result<ConfigurationNode> setQuitPassword(final ConfigurationNode node, final String quitPassword) {
return Result.tryCatch(() -> {
final Long followupId = this.configurationDAO
.getFollowupConfigurationId(node.id)
.getOrThrow();
this.configurationValueDAO.saveQuitPassword(followupId, quitPassword)
.onError(error -> log.warn(
"Failed to reset quit password for configuration: {} cause: {}",
node,
error.getMessage()));
final Configuration config = this.configurationDAO
.getConfigurationLastStableVersion(node.id)
.getOrThrow();
this.configurationValueDAO.saveQuitPassword(config.id, quitPassword)
.onError(error -> log.warn(
"Failed to reset quit password for configuration: {} cause: {}",
node,
error.getMessage()));
return node;
});
}
@Override
public Result<ConfigurationNode> resetToTemplateSettings(final ConfigurationNode configurationNode) {
return Result.tryCatch(() -> {

View file

@ -554,6 +554,7 @@ public class ExamSessionServiceImpl implements ExamSessionService {
.getOr(false);
if (!BooleanUtils.toBoolean(isUpToDate)) {
// TODO this should only flush the exam cache but not the SEB connection cache
return flushCache(exam);
} else {
return Result.of(exam);
@ -570,13 +571,11 @@ public class ExamSessionServiceImpl implements ExamSessionService {
return Result.tryCatch(() -> {
this.examSessionCacheService.evict(exam);
this.examSessionCacheService.evictDefaultSEBConfig(exam.id);
// evict client connection
this.clientConnectionDAO
.getConnectionTokens(exam.id)
.getOrElse(Collections::emptyList)
.forEach(token -> {
// evict client connection
this.examSessionCacheService.evictClientConnection(token);
});
.forEach(this.examSessionCacheService::evictClientConnection);
return exam;
});

View file

@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
@ -64,13 +65,6 @@ 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.impl.SEBServerUser;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
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.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.OrientationDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ViewDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigTemplateService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService;
@ -91,6 +85,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
private final ExamConfigService sebExamConfigService;
private final ExamConfigUpdateService examConfigUpdateService;
private final ExamConfigTemplateService sebExamConfigTemplateService;
private final ConfigurationValueDAO configurationValueDAO;
protected ConfigurationNodeController(
final AuthorizationService authorization,
@ -105,7 +100,8 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
final OrientationDAO orientationDAO,
final ExamConfigService sebExamConfigService,
final ExamConfigUpdateService examConfigUpdateService,
final ExamConfigTemplateService sebExamConfigTemplateService) {
final ExamConfigTemplateService sebExamConfigTemplateService,
final ConfigurationValueDAO configurationValueDAO) {
super(authorization,
bulkActionService,
@ -122,6 +118,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
this.sebExamConfigService = sebExamConfigService;
this.examConfigUpdateService = examConfigUpdateService;
this.sebExamConfigTemplateService = sebExamConfigTemplateService;
this.configurationValueDAO = configurationValueDAO;
}
@Override
@ -312,7 +309,6 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
this.checkModifyPrivilege(institutionId);
final SEBServerUser currentUser = this.authorization.getUserService().getCurrentUser();
final ConfigurationNode configurationNode = new ConfigurationNode(
null,
institutionId,
@ -338,8 +334,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
EntityType.CONFIGURATION_NODE))));
}
final Configuration config = doImport
.getOrThrow();
final Configuration config = doImport.getOrThrow();
// user log
this.configurationNodeDAO.byPK(config.configurationNodeId)
@ -387,6 +382,27 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_SET_QUIT_PWD_PATH_SEGMENT,
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ConfigurationNode setQuitPassword(
@PathVariable final Long modelId,
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@RequestParam(name = API.QUIT_PASSWORD_ATTR_NAME, required = false) final String quitPassword) {
return this.entityDAO.byPK(modelId)
.flatMap(this.authorization::checkModify)
.flatMap(configNode ->this.sebExamConfigService.setQuitPassword(configNode, quitPassword))
.getOrThrow();
}
@RequestMapping(
path = API.PARENT_MODEL_ID_VAR_PATH_SEGMENT + API.TEMPLATE_ATTRIBUTE_ENDPOINT,
method = RequestMethod.GET,
@ -600,6 +616,8 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
password)
.getOrThrow();
processPostImport(result);
return Result.of(result);
} catch (final Exception e) {
@ -608,4 +626,8 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
}
}
private void processPostImport(final Configuration config) {
this.configurationValueDAO.applyIgnoreSEBService(config.institutionId, config.id);
}
}

View file

@ -55,10 +55,10 @@ sebserver.webservice.lms.address.alias=lms.mockup.com=lms.address.alias
sebserver.webservice.cache.moodle.course.pageSize=250
# actuator configuration
management.server.port=${server.port}
management.endpoints.web.base-path=/management
management.endpoints.web.exposure.include=logfile,loggers,jolokia
management.endpoints.web.path-mapping.jolokia=jmx
#management.server.port=${server.port}
#management.endpoints.web.base-path=/management
#management.endpoints.web.exposure.include=logfile,loggers,jolokia
#management.endpoints.web.path-mapping.jolokia=jmx
### Open API Documentation
springdoc.api-docs.enabled=true
springdoc.swagger-ui.enabled=true

View file

@ -1012,7 +1012,7 @@ sebserver.clientconfig.form.sebServerFallbackPasswordHash=Fallback Password
sebserver.clientconfig.form.sebServerFallbackPasswordHash.tooltip=A password if set a SEB Client user must provide before SEB starts the fallback procedure
sebserver.clientconfig.form.sebServerFallbackPasswordHash.confirm=Confirm Password
sebserver.clientconfig.form.sebServerFallbackPasswordHash.tooltip.confirm=Please confirm the fallback password
sebserver.clientconfig.form.hashedQuitPassword=Quit Password
sebserver.clientconfig.form.hashedQuitPassword.tooltip=A password if set a SEB user must provide to be able to quit SEB
sebserver.clientconfig.form.hashedQuitPassword.confirm=Confirm Password
sebserver.clientconfig.form.hashedQuitPassword.tooltip.confirm=Please confirm the quit password
@ -1123,12 +1123,16 @@ sebserver.examconfig.action.import.config.text=To import a valid SEB settings co
sebserver.examconfig.action.import.settings.text=To import a valid SEB settings configuration file (.seb) into an existing exam configuration,<br/>please select the file from the local directory and provide a password if the file is protected.<br/>Please note that this import will override the existing SEB settings of this exam configuration<br/>and also try to publish the changes if selected.
sebserver.examconfig.action.import.auto-publish=Publish
sebserver.examconfig.action.import.auto-publish.tooltip=Try to automatically publish the imported changes
sebserver.examconfig.action.import.quitpwd.title=Quit Password in imported Configuration
sebserver.examconfig.action.import.quitpwd.removed=The hashed quit password was removed,<br/>please set a new password below.
sebserver.examconfig.action.import.quitpwd.set=No quit password set,<br/>enter a password below (or click "ok" if none is to be entered).
sebserver.examconfig.action.state-change.confirm=This configuration is already attached to an exam.<br/>Please note that changing an attached configuration will take effect on the exam when the configuration changes are saved<br/><br/>Are you sure to change this configuration to an editable state?
sebserver.examconfig.message.error.file=Please select a valid SEB Exam Configuration File
sebserver.examconfig.message.confirm.delete=This will completely delete the exam configuration.<br/><br/>Are you sure you want to delete this exam configuration?
sebserver.examconfig.message.consistency.error=The exam configuration cannot be deleted since it is used by at least one running or upcoming exam.<br/>Please remove the exam configuration from running and upcoming exams first.
sebserver.examconfig.message.delete.confirm=The exam configuration ({0}) was successfully deleted.
sebserver.examconfig.message.delete.partialerror=The exam configuration ({0}) was deleted but there where some dependency errors:<br/><br/>{1}
sebserver.examconfig.message.delete.partialerror=The exam configuration ({0}) was deleted but there were some dependency errors:<br/><br/>{1}
sebserver.examconfig.action.restore.template.settings=Reset To Template Settings
sebserver.examconfig.action.restore.template.settings.success=Configuration settings successfully restored to template defaults
sebserver.examconfig.action.restore.template.settings.confirm=Are you sure to reset this configuration setting to the templates settings?
@ -1157,7 +1161,7 @@ sebserver.examconfig.status.READY_TO_USE=Ready To Use
sebserver.examconfig.status.IN_USE=In Use
sebserver.examconfig.status.ARCHIVED=Archived
sebserver.examconfig.props.from.unpublished.message=Note: There are unpublished changes to this Settings. Use 'Save/Publish Settings' to make sure the settings are active.
sebserver.examconfig.props.from.unpublished.message=Note: There are unpublished changes to these Settings. Use 'Save/Publish Settings' to make sure the settings are active.
sebserver.examconfig.props.from.title=SEB Settings ({0})
sebserver.examconfig.props.from.title.subtitle=
sebserver.examconfig.props.form.views.general=General