From c301016d8770cbd75bafdf57bfe5dd461cb53757 Mon Sep 17 00:00:00 2001 From: anhefti Date: Thu, 21 Dec 2023 13:24:39 +0100 Subject: [PATCH] SEBSERV-495 implementation and fix special chars in password --- .../ch/ethz/seb/sebserver/gbl/api/API.java | 2 + .../configs/SEBExamConfigImportPopup.java | 113 +++++++++++++++--- .../examconfig/impl/AbstractInputField.java | 3 +- .../gui/service/page/PageService.java | 3 + .../service/page/impl/ModalInputDialog.java | 2 + .../service/page/impl/PageServiceImpl.java | 7 +- .../remote/webservice/api/RestCall.java | 1 + .../examconfig/ResetImportQuitPassword.java | 32 +++++ .../gui/widget/PasswordConfirmInput.java | 106 ++++++++++++++++ .../sebserver/gui/widget/PasswordInput.java | 2 +- .../servicelayer/dao/ConfigurationDAO.java | 12 +- .../dao/ConfigurationValueDAO.java | 11 ++ .../dao/impl/ConfigurationValueDAOImpl.java | 72 ++++++++++- .../sebconfig/ExamConfigService.java | 6 + .../sebconfig/impl/ExamConfigServiceImpl.java | 38 +++++- .../session/impl/ExamSessionServiceImpl.java | 7 +- .../api/ConfigurationNodeController.java | 44 +++++-- .../config/application-dev-ws.properties | 8 +- src/main/resources/messages.properties | 10 +- 19 files changed, 424 insertions(+), 55 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/ResetImportQuitPassword.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/widget/PasswordConfirmInput.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index 4f2c7bcd..0beff3fd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -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"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigImportPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigImportPopup.java index 185773d8..b03a3809 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigImportPopup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/configs/SEBExamConfigImportPopup.java @@ -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 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 dialog = new ModalInputDialog<>( + context.getShell(), + this.pageService.getWidgetFactory()); + + final ModalInputDialogComposer 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 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 formHandle, final Result 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> { private final PageService pageService; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractInputField.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractInputField.java index 7e556dcd..7eec6c0b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractInputField.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/AbstractInputField.java @@ -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 implements InputFiel return; } this.errorLabel.setVisible(false); - this.errorLabel.setText(""); + this.errorLabel.setText(StringUtils.EMPTY); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java index be586fab..da4481eb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/PageService.java @@ -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 diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ModalInputDialog.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ModalInputDialog.java index 58a717c5..ce554350 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ModalInputDialog.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ModalInputDialog.java @@ -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 extends Dialog { finishUp(this.shell); } + public void open( final LocTextKey title, final Predicate okCallback, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java index 46305862..921afff6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/PageServiceImpl.java @@ -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 diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java index 483e48cf..96fc9209 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/RestCall.java @@ -430,6 +430,7 @@ public abstract class RestCall { + this.queryParams + ", uriVariables=" + this.uriVariables + "]"; } + } public static final class TypeKey { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/ResetImportQuitPassword.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/ResetImportQuitPassword.java new file mode 100644 index 00000000..355462cd --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/examconfig/ResetImportQuitPassword.java @@ -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 { + + public ResetImportQuitPassword() { + super(new TypeKey<>( + CallType.UNDEFINED, + EntityType.CONFIGURATION_NODE, + new TypeReference() { + }), + HttpMethod.POST, + MediaType.APPLICATION_FORM_URLENCODED, + API.CONFIGURATION_NODE_ENDPOINT + + API.MODEL_ID_VAR_PATH_SEGMENT + + API.CONFIGURATION_SET_QUIT_PWD_PATH_SEGMENT); + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/PasswordConfirmInput.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/PasswordConfirmInput.java new file mode 100644 index 00000000..7369e9f7 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/PasswordConfirmInput.java @@ -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; + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/PasswordInput.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/PasswordInput.java index 2e1dfbd8..175e2f63 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/PasswordInput.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/PasswordInput.java @@ -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); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java index a04e67a4..7dd5b84f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationDAO.java @@ -36,7 +36,7 @@ public interface ConfigurationDAO extends EntityDAO 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. - * + *

* 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 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. - * + *

* 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 getFollowupConfigurationId(Long configNodeId); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationValueDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationValueDAO.java index 3fce151f..6293abdb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationValueDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/ConfigurationValueDAO.java @@ -78,4 +78,15 @@ public interface ConfigurationValueDAO extends EntityDAO 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 saveQuitPassword(Long configurationId, String pwd); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java index b03db180..e1813277 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ConfigurationValueDAOImpl.java @@ -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> allOf(final Set pks) { @@ -276,6 +307,37 @@ public class ConfigurationValueDAOImpl implements ConfigurationValueDAO { .onError(TransactionHandler::rollback); } + @Override + @Transactional + public Result 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> 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> delete(final Set all) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ExamConfigService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ExamConfigService.java index e8e09611..714c726c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ExamConfigService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ExamConfigService.java @@ -158,4 +158,10 @@ public interface ExamConfigService { * @return Result refer to the given ConfigurationNode or to an error if the check has failed */ Result 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 setQuitPassword(ConfigurationNode node, String quitPassword); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java index e19d4749..0fad2096 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ExamConfigServiceImpl.java @@ -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 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 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 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 resetToTemplateSettings(final ConfigurationNode configurationNode) { return Result.tryCatch(() -> { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java index b8898d5c..a3155dda 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java @@ -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; }); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationNodeController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationNodeController.java index 15263f08..996fc868 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationNodeController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ConfigurationNodeController.java @@ -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 EntityControllerthis.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 EntityControllerplease select the file from the local directory and provide a password if the file is protected.
Please note that this import will override the existing SEB settings of this exam configuration
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,
please set a new password below. +sebserver.examconfig.action.import.quitpwd.set=No quit password set,
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.
Please note that changing an attached configuration will take effect on the exam when the configuration changes are saved

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.

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.
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:

{1} +sebserver.examconfig.message.delete.partialerror=The exam configuration ({0}) was deleted but there were some dependency errors:

{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