From 6c8aa7b12c7d99791a75b7f6c5e6058bd5222dc0 Mon Sep 17 00:00:00 2001 From: anhefti Date: Tue, 27 Oct 2020 13:13:43 +0100 Subject: [PATCH] fixed client config export to configure a client with password added possibility to export client config on exam side with including examId in the config --- .../content/ExamCreateClientConfigPopup.java | 176 ++++++++++++++++++ .../seb/sebserver/gui/content/ExamForm.java | 9 + .../gui/content/SEBClientConfigForm.java | 24 +-- .../gui/content/action/ActionDefinition.java | 5 + .../download/SEBClientConfigDownload.java | 14 +- .../seb/clientconfig/GetClientConfigs.java | 38 ++++ .../dao/impl/SEBClientConfigDAOImpl.java | 18 +- .../sebconfig/ClientConfigService.java | 6 +- .../impl/ClientConfigServiceImpl.java | 65 +++++-- .../sebconfig/impl/PasswordEncryptor.java | 1 + .../api/SEBClientConfigController.java | 6 +- src/main/resources/messages.properties | 7 + 12 files changed, 334 insertions(+), 35 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/ExamCreateClientConfigPopup.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/clientconfig/GetClientConfigs.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamCreateClientConfigPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamCreateClientConfigPopup.java new file mode 100644 index 00000000..7625bbe5 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamCreateClientConfigPopup.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2020 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.gui.content; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.client.service.UrlLauncher; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigCreationInfo; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig.ConfigPurpose; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gbl.util.Tuple; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.gui.form.FormBuilder; +import ch.ethz.seb.sebserver.gui.form.FormHandle; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer; +import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.page.PageService; +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.download.DownloadService; +import ch.ethz.seb.sebserver.gui.service.remote.download.SEBClientConfigDownload; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.GetClientConfigs; + +@Lazy +@Component +@GuiProfile +public class ExamCreateClientConfigPopup { + + private static final LocTextKey TITLE_KEY = new LocTextKey("sebserver.exam.form.export.config.popup.title"); + private static final LocTextKey CONFIG_NAME_KEY = new LocTextKey("sebserver.exam.form.export.config.name"); + private static final LocTextKey CONFIG_TEXT_KEY = new LocTextKey("sebserver.exam.form.export.config.popup.text"); + private static final LocTextKey NO_CONFIG_TEXT_KEY = + new LocTextKey("sebserver.exam.form.export.config.popup.noconfig"); + + private final PageService pageService; + private final DownloadService downloadService; + private final String downloadFileName; + + public ExamCreateClientConfigPopup( + final PageService pageService, + final DownloadService downloadService, + @Value("${sebserver.gui.seb.exam.config.download.filename}") final String downloadFileName) { + + this.pageService = pageService; + this.downloadService = downloadService; + this.downloadFileName = downloadFileName; + } + + public Function exportFunction() { + + return action -> { + + final ModalInputDialog> dialog = + new ModalInputDialog>( + action.pageContext().getParent().getShell(), + this.pageService.getWidgetFactory()) + .setLargeDialogWidth(); + + final CreationFormContext creationFormContext = new CreationFormContext( + this.pageService, + action.pageContext()); + + final Predicate> doCreate = formHandle -> doCreate( + this.pageService, + action.pageContext(), + action.getEntityKey(), + formHandle); + + dialog.open( + TITLE_KEY, + doCreate, + Utils.EMPTY_EXECUTION, + creationFormContext); + + return action; + }; + } + + private boolean doCreate( + final PageService pageService, + final PageContext pageContext, + final EntityKey examKey, + final FormHandle formHandle) { + + if (formHandle == null) { + return true; + } + + final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class); + final String modelId = formHandle.getForm().getFieldValue(Domain.SEB_CLIENT_CONFIGURATION.ATTR_ID); + final String downloadURL = this.downloadService.createDownloadURL( + modelId, + examKey.modelId, + SEBClientConfigDownload.class, + this.downloadFileName); + urlLauncher.openURL(downloadURL); + + return true; + } + + private final class CreationFormContext implements ModalInputDialogComposer> { + + private final PageService pageService; + private final PageContext pageContext; + + protected CreationFormContext( + final PageService pageService, + final PageContext pageContext) { + + this.pageService = pageService; + this.pageContext = pageContext; + } + + @Override + public Supplier> compose(final Composite parent) { + + final List> configs = this.pageService.getRestService().getBuilder(GetClientConfigs.class) + .withQueryParam(SEBClientConfig.FILTER_ATTR_ACTIVE, Constants.TRUE_STRING) + .call() + .getOrThrow() + .stream() + .filter(config -> config.configPurpose == ConfigPurpose.START_EXAM) + .map(config -> new Tuple<>(config.getModelId(), config.name)) + .collect(Collectors.toList()); + + if (configs.isEmpty()) { + final Label text = this.pageService + .getWidgetFactory() + .labelLocalized(parent, NO_CONFIG_TEXT_KEY); + text.setData(RWT.MARKUP_ENABLED, true); + return null; + } else { + + final Label text = this.pageService + .getWidgetFactory() + .labelLocalized(parent, CONFIG_TEXT_KEY); + text.setData(RWT.MARKUP_ENABLED, true); + + final FormHandle formHandle = this.pageService.formBuilder( + this.pageContext.copyOf(parent)) + .readonly(false) + .addField(FormBuilder.singleSelection( + Domain.SEB_CLIENT_CONFIGURATION.ATTR_ID, + CONFIG_NAME_KEY, + configs.get(0)._1, + () -> configs)) + + .build(); + return () -> formHandle; + } + } + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java index c9ab06dd..a0f02401 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java @@ -166,11 +166,13 @@ public class ExamForm implements TemplateComposer { private final String downloadFileName; private final WidgetFactory widgetFactory; private final RestService restService; + private final ExamCreateClientConfigPopup examCreateClientConfigPopup; protected ExamForm( final PageService pageService, final ResourceService resourceService, final DownloadService downloadService, + final ExamCreateClientConfigPopup examCreateClientConfigPopup, @Value("${sebserver.gui.seb.exam.config.download.filename}") final String downloadFileName) { this.pageService = pageService; @@ -179,6 +181,7 @@ public class ExamForm implements TemplateComposer { this.downloadFileName = downloadFileName; this.widgetFactory = pageService.getWidgetFactory(); this.restService = this.resourceService.getRestService(); + this.examCreateClientConfigPopup = examCreateClientConfigPopup; this.consistencyMessageMapping = new HashMap<>(); this.consistencyMessageMapping.put( @@ -245,6 +248,7 @@ public class ExamForm implements TemplateComposer { final boolean modifyGrant = userGrantCheck.m(); final ExamStatus examStatus = exam.getStatus(); final boolean isExamRunning = examStatus == ExamStatus.RUNNING; + final boolean writeGrant = userGrantCheck.w(); final boolean editable = examStatus == ExamStatus.UP_COMING || examStatus == ExamStatus.RUNNING && currentUser.get().hasRole(UserRole.EXAM_ADMIN); @@ -391,6 +395,11 @@ public class ExamForm implements TemplateComposer { .withExec(this.cancelModifyFunction()) .publishIf(() -> !readonly) + .newAction(ActionDefinition.EXAM_SEB_CLIENT_CONFIG_EXPORT) + .withEntityKey(entityKey) + .withExec(this.examCreateClientConfigPopup.exportFunction()) + .publishIf(() -> writeGrant && readonly) + .newAction(ActionDefinition.EXAM_MODIFY_SEB_RESTRICTION_DETAILS) .withEntityKey(entityKey) .withExec(ExamSEBRestrictionSettings.settingsFunction(this.pageService)) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientConfigForm.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientConfigForm.java index d08b4a39..dae5b512 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientConfigForm.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SEBClientConfigForm.java @@ -306,17 +306,19 @@ public class SEBClientConfigForm implements TemplateComposer { FALLBACK_ATTRIBUTES::contains, ffa -> ffa.setVisible(BooleanUtils.isTrue(clientConfig.fallback))); - formHandle.getForm().getFieldInput(SEBClientConfig.ATTR_FALLBACK) - .addListener(SWT.Selection, event -> formHandle.process( - FALLBACK_ATTRIBUTES::contains, - ffa -> { - final boolean selected = ((Button) event.widget).getSelection(); - ffa.setVisible(selected); - if (!selected && ffa.hasError()) { - ffa.resetError(); - ffa.setStringValue(StringUtils.EMPTY); - } - })); + if (!isReadonly) { + formHandle.getForm().getFieldInput(SEBClientConfig.ATTR_FALLBACK) + .addListener(SWT.Selection, event -> formHandle.process( + FALLBACK_ATTRIBUTES::contains, + ffa -> { + final boolean selected = ((Button) event.widget).getSelection(); + ffa.setVisible(selected); + if (!selected && ffa.hasError()) { + ffa.resetError(); + ffa.setStringValue(StringUtils.EMPTY); + } + })); + } final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class); this.pageService.pageActionBuilder(formContext.clearEntityKeys()) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index ad20c76a..5442ef3e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -353,6 +353,11 @@ public enum ActionDefinition { ImageIcon.CANCEL, PageStateDefinitionImpl.EXAM_VIEW, ActionCategory.FORM), + EXAM_SEB_CLIENT_CONFIG_EXPORT( + new LocTextKey("sebserver.exam.action.createClientToStartExam"), + ImageIcon.EXPORT, + PageStateDefinitionImpl.EXAM_VIEW, + ActionCategory.FORM), SEB_CLIENT_CONFIG_LIST( new LocTextKey("sebserver.clientconfig.list.title"), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBClientConfigDownload.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBClientConfigDownload.java index a7898eec..6875b70b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBClientConfigDownload.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/download/SEBClientConfigDownload.java @@ -13,6 +13,7 @@ import java.io.InputStream; import java.io.OutputStream; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -20,7 +21,9 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.ExportClientConfig; @@ -45,8 +48,15 @@ public class SEBClientConfigDownload extends AbstractDownloadServiceHandler { @Override protected void webserviceCall(final String modelId, final String parentModelId, final OutputStream downloadOut) { - final InputStream input = this.restService.getBuilder(ExportClientConfig.class) - .withURIVariable(API.PARAM_MODEL_ID, modelId) + final RestCall.RestCallBuilder restCallBuilder = this.restService + .getBuilder(ExportClientConfig.class) + .withURIVariable(API.PARAM_MODEL_ID, modelId); + + if (StringUtils.isNotBlank(parentModelId)) { + restCallBuilder.withQueryParam(EXAM.ATTR_ID, parentModelId); + } + + final InputStream input = restCallBuilder .call() .getOrThrow(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/clientconfig/GetClientConfigs.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/clientconfig/GetClientConfigs.java new file mode 100644 index 00000000..9da730e9 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/seb/clientconfig/GetClientConfigs.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 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.gui.service.remote.webservice.api.seb.clientconfig; + +import java.util.List; + +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import com.fasterxml.jackson.core.type.TypeReference; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.PageToListCallAdapter; + +@Lazy +@Component +@GuiProfile +public class GetClientConfigs extends PageToListCallAdapter { + + public GetClientConfigs() { + super( + GetClientConfigPage.class, + EntityType.SEB_CLIENT_CONFIGURATION, + new TypeReference>() { + }, + API.SEB_CLIENT_CONFIG_ENDPOINT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/SEBClientConfigDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/SEBClientConfigDAOImpl.java index abf0523c..5496d2e2 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/SEBClientConfigDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/SEBClientConfigDAOImpl.java @@ -31,9 +31,9 @@ import org.springframework.transaction.annotation.Transactional; import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage; +import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService; import ch.ethz.seb.sebserver.gbl.client.ClientCredentials; -import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig; @@ -245,18 +245,20 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO { checkUniqueName(sebClientConfig); + final SebClientConfigRecord record = + this.sebClientConfigRecordMapper.selectByPrimaryKey(sebClientConfig.id); + final SebClientConfigRecord newRecord = new SebClientConfigRecord( sebClientConfig.id, - null, + record.getInstitutionId(), sebClientConfig.name, - null, - null, - null, + record.getDate(), + record.getClientName(), + record.getClientSecret(), getEncryptionPassword(sebClientConfig), - null); + record.getActive()); - this.sebClientConfigRecordMapper - .updateByPrimaryKeySelective(newRecord); + this.sebClientConfigRecordMapper.updateByPrimaryKey(newRecord); saveAdditionalAttributes(sebClientConfig, newRecord.getId()); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ClientConfigService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ClientConfigService.java index 8f1f6db4..1f50138a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ClientConfigService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/ClientConfigService.java @@ -45,10 +45,12 @@ public interface ClientConfigService { * as described here: https://www.safeexambrowser.org/developer/seb-file-format.html * * @param out OutputStream to write the export to - * @param modelId the model identifier of the SEBClientConfiguration to export */ + * @param modelId the model identifier of the SEBClientConfiguration to export + * @param examId The exam identifier. May be null, if not the exported client config will contain the exam information*/ void exportSEBClientConfiguration( OutputStream out, - final String modelId); + final String modelId, + final Long examId); /** Get the ClientDetails for given client name that identifies a SEBClientConfiguration entry. * diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ClientConfigServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ClientConfigServiceImpl.java index a3986c98..a06e63a5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ClientConfigServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/ClientConfigServiceImpl.java @@ -13,6 +13,8 @@ import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.Collection; import java.util.Collections; @@ -21,6 +23,7 @@ import java.util.UUID; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.util.encoders.Hex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; @@ -46,6 +49,7 @@ import ch.ethz.seb.sebserver.gbl.client.ClientCredentialService; import ch.ethz.seb.sebserver.gbl.client.ClientCredentials; import ch.ethz.seb.sebserver.gbl.model.institution.Institution; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig; +import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig.ConfigPurpose; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Utils; @@ -66,6 +70,7 @@ public class ClientConfigServiceImpl implements ClientConfigService { private static final Logger log = LoggerFactory.getLogger(ClientConfigServiceImpl.class); + private static final String SEB_CLIENT_CONFIG_EXAM_PROP_NAME = "exam"; private static final String SEB_CLIENT_CONFIG_TEMPLATE_XML = " %n" + " sebMode%n" + @@ -80,7 +85,7 @@ public class ClientConfigServiceImpl implements ClientConfigService { " sebServerConfiguration%n" + " %n" + " institution%n" + - " %s%n" + + " %s%n%s" + " clientName%n" + " %s%n" + " clientSecret%n" + @@ -186,16 +191,17 @@ public class ClientConfigServiceImpl implements ClientConfigService { @Override public void exportSEBClientConfiguration( final OutputStream output, - final String modelId) { + final String modelId, + final Long examId) { final SEBClientConfig config = this.sebClientConfigDAO .byModelId(modelId).getOrThrow(); final CharSequence encryptionPassword = this.sebClientConfigDAO .getConfigPasswordCipher(config.getModelId()) - .getOr(StringUtils.EMPTY); + .getOr((config.getConfigPurpose() == ConfigPurpose.START_EXAM) ? null : StringUtils.EMPTY); - final String plainTextXMLContent = extractXMLContent(config); + final String plainTextXMLContent = extractXMLContent(config, examId); PipedOutputStream pOut = null; PipedInputStream pIn = null; @@ -223,7 +229,7 @@ public class ClientConfigServiceImpl implements ClientConfigService { if (encryptionPassword != null) { // encrypt zipped plain text and add header - passwordEncryption(zipOut, encryptionPassword, pIn); + passwordEncryption(zipOut, encryptionPassword, config.getConfigPurpose(), pIn); } else { // just add plain text header this.sebConfigEncryptionService.streamEncrypted( @@ -248,7 +254,7 @@ public class ClientConfigServiceImpl implements ClientConfigService { } } - private String extractXMLContent(final SEBClientConfig config) { + private String extractXMLContent(final SEBClientConfig config, final Long examId) { String fallbackAddition = ""; if (BooleanUtils.isTrue(config.fallback)) { @@ -289,6 +295,14 @@ public class ClientConfigServiceImpl implements ClientConfigService { } } + String examIdAddition = ""; + if (examId != null) { + examIdAddition = String.format( + SEB_CLIENT_CONFIG_STRING_TEMPLATE, + SEB_CLIENT_CONFIG_EXAM_PROP_NAME, + examId); + } + final ClientCredentials sebClientCredentials = this.sebClientConfigDAO .getSEBClientCredentials(config.getModelId()) .getOrThrow(); @@ -305,6 +319,7 @@ public class ClientConfigServiceImpl implements ClientConfigService { fallbackAddition, this.webserviceInfo.getExternalServerURL(), config.institutionId, + examIdAddition, plainClientId, plainClientSecret, this.webserviceInfo.getDiscoveryEndpoint()); @@ -375,22 +390,50 @@ public class ClientConfigServiceImpl implements ClientConfigService { private void passwordEncryption( final OutputStream output, final CharSequence encryptionPassword, + final ConfigPurpose configPurpose, final InputStream input) { if (log.isDebugEnabled()) { log.debug("*** SEB client configuration with password based encryption"); } - final CharSequence encryptionPasswordPlaintext = (encryptionPassword == StringUtils.EMPTY) - ? StringUtils.EMPTY - : this.clientCredentialService.decrypt(encryptionPassword); + final CharSequence plainTextPassword = getPlainTextPassword( + encryptionPassword, + configPurpose); this.sebConfigEncryptionService.streamEncrypted( output, input, EncryptionContext.contextOf( - (encryptionPassword == StringUtils.EMPTY) ? Strategy.PASSWORD_PWCC : Strategy.PASSWORD_PSWD, - encryptionPasswordPlaintext)); + (configPurpose == ConfigPurpose.CONFIGURE_CLIENT) + ? Strategy.PASSWORD_PWCC + : Strategy.PASSWORD_PSWD, + plainTextPassword)); + } + + private CharSequence getPlainTextPassword( + final CharSequence encryptionPassword, + final ConfigPurpose configPurpose) { + + CharSequence plainTextPassword = (encryptionPassword == StringUtils.EMPTY) + ? StringUtils.EMPTY + : this.clientCredentialService.decrypt(encryptionPassword); + + if (configPurpose == ConfigPurpose.CONFIGURE_CLIENT && plainTextPassword != StringUtils.EMPTY) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-256"); + final byte[] hash = digest.digest( + plainTextPassword.toString().getBytes(StandardCharsets.UTF_8)); + final byte[] encode = Hex.encode(hash); + plainTextPassword = new String(encode, StandardCharsets.UTF_8); + + } catch (final NoSuchAlgorithmException e) { + log.error("Failed to generate password hash for config encryption.", e); + plainTextPassword = StringUtils.EMPTY; + } + } + return plainTextPassword; } /** Get a encoded clientSecret for the SEBClientConfiguration with specified clientId/clientName. diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordEncryptor.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordEncryptor.java index adbd4569..1974cd70 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordEncryptor.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/sebconfig/impl/PasswordEncryptor.java @@ -69,6 +69,7 @@ public class PasswordEncryptor implements SEBConfigCryptor { try { final CharSequence password = context.getPassword(); + if (password.length() == 0) { encryptOutput = new AES256JNCryptorOutputStreamEmptyPwdSupport( output, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SEBClientConfigController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SEBClientConfigController.java index 17304a2b..fd3430e1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SEBClientConfigController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/SEBClientConfigController.java @@ -30,6 +30,7 @@ import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import ch.ethz.seb.sebserver.gbl.Constants; @@ -37,6 +38,7 @@ import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig; import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; @@ -83,6 +85,7 @@ public class SEBClientConfigController extends ActivatableEntityController
This has only de sebserver.exam.form.supporter=Exam Supporter sebserver.exam.form.supporter.tooltip=A list of users that are allowed to support this exam

To add a user in edit mode click into the field on the right-hand side and start typing the first letters of the username.
A filtered choice will drop down. Select a specific username in the dropdown list to add the user to the list.
To remove a user from the list, just double-click the username on the list. +sebserver.exam.form.export.config.popup.title=Export SEB Client Configuration for Starting the Exam +sebserver.exam.form.export.config.name=Name +sebserver.exam.form.export.config.name.tooltip=The name of the SEB Client Configuration +sebserver.exam.form.export.config.popup.text=Please select the SEB Client Configuration you want to use for starting this exam
and click OK to start the download. +sebserver.exam.form.export.config.popup.noconfig=There is currently no active SEB Client Configuration for the purpose of "Starting an Exam".

Please go the SEB Client Configuration section and create a new one." + sebserver.exam.form.sebrestriction.title=SEB Restriction Details sebserver.exam.form.sebrestriction.title.subtitle= sebserver.exam.form.sebrestriction.info=Info