asynchronous zip end encryption streaming for seb configs download

This commit is contained in:
anhefti 2019-05-11 16:47:10 +02:00
parent 8867721a8a
commit 06da2d026b
25 changed files with 773 additions and 119 deletions

View file

@ -31,4 +31,9 @@ public class AsyncRunner {
return new AsyncResult<>(supplier.get());
}
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
public void runAsync(final Runnable block) {
block.run();
}
}

View file

@ -8,8 +8,16 @@
package ch.ethz.seb.sebserver.gbl.async;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@ -17,6 +25,8 @@ import org.springframework.stereotype.Service;
@Service
public class AsyncService {
private static final Logger log = LoggerFactory.getLogger(AsyncService.class);
private final AsyncRunner asyncRunner;
protected AsyncService(final AsyncRunner asyncRunner) {
@ -62,4 +72,41 @@ public class AsyncService {
momoized);
}
public void pipeToOutputStream(
final OutputStream output,
final Consumer<PipedOutputStream> consumer) {
this.asyncRunner.runAsync(() -> {
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
consumer.accept(pout);
IOUtils.copyLarge(pin, output);
pin.close();
pout.flush();
pout.close();
} catch (final IOException e) {
log.error("Error while pipe stream data: ", e);
} finally {
try {
pin.close();
} catch (final IOException e1) {
log.error("Failed to close PipedInputStream: ", e1);
}
try {
pout.close();
} catch (final IOException e1) {
log.error("Failed to close PipedOutputStream: ", e1);
}
}
});
}
}

View file

@ -12,10 +12,13 @@ import java.util.concurrent.Executor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
public class AsyncServiceSpringConfig {
@EnableAsync
public class AsyncServiceSpringConfig implements AsyncConfigurer {
public static final String EXECUTOR_BEAN_NAME = "AsyncServiceExecutorBean";
@ -30,4 +33,9 @@ public class AsyncServiceSpringConfig {
return executor;
}
@Override
public Executor getAsyncExecutor() {
return threadPoolTaskExecutor();
}
}

View file

@ -41,7 +41,8 @@ public enum AttributeType {
/** Table type is a list of a composite of single types */
TABLE(COMPOSITE_LIST),
;
STATIC_TABLE(COMPOSITE_LIST);
public final AttributeValueType attributeValueType;

View file

@ -14,6 +14,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
/** Adapter interface for SEB Exam Configuration based input fields. */
public interface InputField {
ConfigurationAttribute getAttribute();

View file

@ -0,0 +1,132 @@
/*
* Copyright (c) 2019 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.examconfig.impl;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
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.sebconfig.AttributeType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.examconfig.ExamConfigurationService;
import ch.ethz.seb.sebserver.gui.service.examconfig.InputField;
import ch.ethz.seb.sebserver.gui.service.examconfig.InputFieldBuilder;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.Selection;
import ch.ethz.seb.sebserver.gui.widget.SingleSelection;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@Component
@GuiProfile
public class SingleSelectionFieldBuilder implements InputFieldBuilder {
private final WidgetFactory widgetFactory;
protected SingleSelectionFieldBuilder(final WidgetFactory widgetFactory) {
this.widgetFactory = widgetFactory;
}
@Override
public boolean builderFor(
final ConfigurationAttribute attribute,
final Orientation orientation) {
return attribute.type == AttributeType.SINGLE_SELECTION;
}
@Override
public InputField createInputField(
final Composite parent,
final ConfigurationAttribute attribute,
final ViewContext viewContext) {
final Orientation orientation = viewContext
.getOrientation(attribute.id);
final Composite innerGrid = InputFieldBuilder
.createInnerGrid(parent, orientation);
final SingleSelection selection = this.widgetFactory.selectionLocalized(
Selection.Type.SINGLE,
innerGrid,
() -> this.getLocalizedResources(attribute))
.getTypeInstance();
final SingleSelectionInputField singleSelectionInputField = new SingleSelectionInputField(
attribute,
orientation,
selection,
InputFieldBuilder.createErrorLabel(innerGrid));
selection.setSelectionListener(event -> {
singleSelectionInputField.clearError();
viewContext.getValueChangeListener().valueChanged(
viewContext,
attribute,
String.valueOf(selection.getSelectionValue()),
singleSelectionInputField.listIndex);
});
return null;
}
private List<Tuple<String>> getLocalizedResources(final ConfigurationAttribute attribute) {
if (attribute == null) {
return Collections.emptyList();
}
final I18nSupport i18nSupport = this.widgetFactory.getI18nSupport();
final String prefix = ExamConfigurationService.ATTRIBUTE_LABEL_LOC_TEXT_PREFIX + attribute.name + ".";
return Arrays.asList(StringUtils.split(
attribute.resources,
Constants.LIST_SEPARATOR))
.stream()
.map(value -> new Tuple<>(
value,
i18nSupport.getText(
new LocTextKey(prefix + value),
value)))
.collect(Collectors.toList());
}
static final class SingleSelectionInputField extends AbstractInputField<SingleSelection> {
protected SingleSelectionInputField(
final ConfigurationAttribute attribute,
final Orientation orientation,
final SingleSelection control,
final Label errorLabel) {
super(attribute, orientation, control, errorLabel);
}
@Override
public String getValue() {
return this.control.getSelectionValue();
}
@Override
protected void setValueToControl(final String value) {
this.control.select(value);
}
}
}

View file

@ -270,17 +270,6 @@ public class TableFieldBuilder implements InputFieldBuilder {
}
// private void applyRowValues(
// final int rowIndex,
// final Map<Long, TableValue> rowValues) {
//
// // set the new values
// this.values.set(rowIndex, rowValues);
// // update table row
// applyTableRowValues(rowIndex);
//
// }
private void applyTableRowValues(final int index) {
final TableItem item = this.control.getItem(index);
final Map<Long, TableValue> rowValues = this.values.get(index);

View file

@ -20,6 +20,7 @@ import org.eclipse.swt.widgets.ColorDialog;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,6 +38,8 @@ public class ColorSelection extends Composite implements Selection {
private final Composite colorField;
private RGB selection;
private Listener listener = null;
ColorSelection(final Composite parent, final WidgetFactory widgetFactory) {
super(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(2, false);
@ -67,6 +70,11 @@ public class ColorSelection extends Composite implements Selection {
this.addListener(SWT.Resize, this::adaptColumnWidth);
}
@Override
public void setSelectionListener(final Listener listener) {
this.listener = listener;
}
@Override
public Type type() {
return Type.COLOR;
@ -100,6 +108,9 @@ public class ColorSelection extends Composite implements Selection {
this.selection = this.colorDialog.getRGB();
applySelection();
if (this.listener != null) {
this.listener.handleEvent(event);
}
});
}

View file

@ -20,6 +20,7 @@ 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;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
@ -34,6 +35,8 @@ public class MultiSelection extends Composite implements Selection {
private final List<Label> labels = new ArrayList<>();
private final List<Label> selected = new ArrayList<>();
private Listener listener = null;
MultiSelection(final Composite parent) {
super(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(1, true);
@ -49,6 +52,11 @@ public class MultiSelection extends Composite implements Selection {
return Type.MULTI;
}
@Override
public void setSelectionListener(final Listener listener) {
this.listener = listener;
}
@Override
public void applyNewMapping(final List<Tuple<String>> mapping) {
final String selectionValue = getSelectionValue();
@ -71,6 +79,9 @@ public class MultiSelection extends Composite implements Selection {
l.setData(RWT.CUSTOM_VARIANT, CustomVariant.SELECTED.key);
this.selected.add(l);
}
if (this.listener != null) {
this.listener.handleEvent(event);
}
});
this.labels.add(label);
}

View file

@ -26,6 +26,7 @@ import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -51,6 +52,8 @@ public class MultiSelectionCombo extends Composite implements Selection {
private final GridData comboCell;
private final GridData actionCell;
private Listener listener = null;
MultiSelectionCombo(final Composite parent, final WidgetFactory widgetFactory) {
super(parent, SWT.NONE);
this.widgetFactory = widgetFactory;
@ -83,6 +86,11 @@ public class MultiSelectionCombo extends Composite implements Selection {
return Type.MULTI_COMBO;
}
@Override
public void setSelectionListener(final Listener listener) {
this.listener = listener;
}
@Override
public void applyNewMapping(final List<Tuple<String>> mapping) {
this.mapping.putAll(mapping.stream()
@ -153,6 +161,9 @@ public class MultiSelectionCombo extends Composite implements Selection {
}
addSelection(findFirst.get().getKey());
if (this.listener != null) {
this.listener.handleEvent(event);
}
}
private void addSelection(final String itemKey) {
@ -203,6 +214,9 @@ public class MultiSelectionCombo extends Composite implements Selection {
this.combo.add(value._2, this.combo.getItemCount());
this.getParent().layout();
if (this.listener != null) {
this.listener.handleEvent(event);
}
}
private void adaptColumnWidth(final Event event) {

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.widget;
import java.util.List;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Listener;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
@ -35,10 +36,15 @@ public interface Selection {
void setVisible(boolean visible);
void setSelectionListener(Listener listener);
default Control adaptToControl() {
return (Control) this;
}
//<T extends Selection> T getTypeInstance();
@SuppressWarnings("unchecked")
default <T extends Selection> T getTypeInstance() {
return (T) this;
}
}

View file

@ -15,6 +15,7 @@ import java.util.stream.Collectors;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Listener;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
@ -77,4 +78,9 @@ public class SingleSelection extends Combo implements Selection {
return Type.SINGLE;
}
@Override
public void setSelectionListener(final Listener listener) {
super.addListener(SWT.Selection, listener);
}
}

View file

@ -8,7 +8,7 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig;
import java.io.InputStream;
import java.io.OutputStream;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Result;
@ -55,6 +55,8 @@ public interface SebClientConfigService {
Result<SebClientConfig> autoCreateSebClientConfigurationForInstitution(Long institutionId);
Result<InputStream> exportSebClientConfiguration(final String modelId);
void exportSebClientConfiguration(
OutputStream out,
final String modelId);
}

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Set;
@ -43,4 +45,14 @@ public interface SebConfigCryptor {
final ByteBuffer cipher,
final SebConfigEncryptionContext context);
void encrypt(
final OutputStream encryptedOutput,
final InputStream plainTextInputStream,
final SebConfigEncryptionContext context);
void decrypt(
final OutputStream plainTextOutput,
final InputStream cipherInputStream,
final SebConfigEncryptionContext context);
}

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.cert.Certificate;
import java.util.function.Function;
@ -60,16 +62,11 @@ public interface SebConfigEncryptionService {
* case */
Result<ByteBuffer> plainText(CharSequence plainTextConfig);
/** Use this to create a password encrypted SEB Configuration file from configuration text
* with the given Strategy
*
* @param plainTextConfig plainTextConfig plain text SEB Configuration as CharSequence
* @return Result of password encoded ByteBuffer or a reference to an Exception on error
* case */
Result<ByteBuffer> encryptWithPassword(
CharSequence plainTextConfig,
Strategy strategy,
CharSequence password);
void streamEncryption(
final OutputStream output,
final InputStream input,
final Strategy strategy,
final CharSequence password);
Result<ByteBuffer> encryptWithCertificate(
CharSequence plainTextConfig,

View file

@ -0,0 +1,19 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig;
import java.io.InputStream;
import java.io.OutputStream;
public interface ZipService {
void write(OutputStream out, InputStream in);
void read(OutputStream out, InputStream in);
}

View file

@ -8,25 +8,37 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Set;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.cryptonode.jncryptor.AES256JNCryptorOutputStream;
import org.cryptonode.jncryptor.CryptorException;
import org.cryptonode.jncryptor.JNCryptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigCryptor;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionContext;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService.Strategy;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigCryptor;
@Lazy
@Component
@WebServiceProfile
public class PasswordEncryptor implements SebConfigCryptor {
private static final Logger log = LoggerFactory.getLogger(PasswordEncryptor.class);
private static final Set<Strategy> STRATEGIES = Utils.immutableSetOf(
Strategy.PASSWORD_PSWD,
Strategy.PASSWORD_PWCC);
@ -60,4 +72,58 @@ public class PasswordEncryptor implements SebConfigCryptor {
});
}
@Override
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
public void encrypt(
final OutputStream output,
final InputStream input,
final SebConfigEncryptionContext context) {
if (log.isDebugEnabled()) {
log.debug("*** Start streaming asynchronous encryption of SEB exam configuration data");
}
AES256JNCryptorOutputStream encryptOutput = null;
try {
encryptOutput = new AES256JNCryptorOutputStream(
output,
Utils.toCharArray(context.getPassword()));
IOUtils.copyLarge(input, encryptOutput);
encryptOutput.close();
encryptOutput.flush();
encryptOutput.close();
output.flush();
} catch (final CryptorException e) {
log.error("Error while trying to stream and encrypt seb exam configuration data: ", e);
} catch (final IOException e) {
log.error("Error while trying to read/write form/to streams: ", e);
} finally {
try {
if (encryptOutput != null)
encryptOutput.close();
} catch (final IOException e) {
log.error("Failed to close AES256JNCryptorOutputStream: ", e);
}
if (log.isDebugEnabled()) {
log.debug("*** Finish streaming asynchronous encryption of SEB exam configuration data");
}
}
}
@Override
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
public void decrypt(
final OutputStream plainTextOutput,
final InputStream cipherInputStream,
final SebConfigEncryptionContext context) {
// TODO Auto-generated method stub
}
}

View file

@ -9,11 +9,16 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.UUID;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
@ -34,6 +39,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SebClientConfigDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebClientConfigService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService.Strategy;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService;
@Lazy
@Service
@ -46,6 +52,7 @@ public class SebClientConfigServiceImpl implements SebClientConfigService {
private final SebClientConfigDAO sebClientConfigDAO;
private final ClientCredentialService clientCredentialService;
private final SebConfigEncryptionService sebConfigEncryptionService;
private final ZipService zipService;
private final String httpScheme;
private final String serverAddress;
private final String serverPort;
@ -56,6 +63,7 @@ public class SebClientConfigServiceImpl implements SebClientConfigService {
final SebClientConfigDAO sebClientConfigDAO,
final ClientCredentialService clientCredentialService,
final SebConfigEncryptionService sebConfigEncryptionService,
final ZipService zipService,
@Value("${sebserver.webservice.http.scheme}") final String httpScheme,
@Value("${server.address}") final String serverAddress,
@Value("${server.port}") final String serverPort,
@ -65,6 +73,7 @@ public class SebClientConfigServiceImpl implements SebClientConfigService {
this.sebClientConfigDAO = sebClientConfigDAO;
this.clientCredentialService = clientCredentialService;
this.sebConfigEncryptionService = sebConfigEncryptionService;
this.zipService = zipService;
this.httpScheme = httpScheme;
this.serverAddress = serverAddress;
this.serverPort = serverPort;
@ -97,77 +106,113 @@ public class SebClientConfigServiceImpl implements SebClientConfigService {
}
@Override
public Result<InputStream> exportSebClientConfiguration(final String modelId) {
return this.sebClientConfigDAO.byModelId(modelId)
.flatMap(this::createExport);
}
public void exportSebClientConfiguration(
final OutputStream output,
final String modelId) {
private final Result<InputStream> createExport(final SebClientConfig config) {
// TODO implementation of creation of SEB client configuration for specified Institution
// A SEB start configuration should at least contain the SEB-Client-Credentials to access the SEB Server API
// and the SEB Server URL
//
// To Clarify : The format of a SEB start configuration
// To Clarify : How the file should be encrypted (use case) maybe we need another encryption-secret for this that can be given by
// an administrator on SEB start configuration creation time
final SebClientConfig config = this.sebClientConfigDAO
.byModelId(modelId).getOrThrow();
return Result.tryCatch(() -> {
final String serverURL = UriComponentsBuilder.newInstance()
.scheme(this.httpScheme)
.host(this.serverAddress)
.port(this.serverPort)
.toUriString();
final String serverURL = UriComponentsBuilder.newInstance()
.scheme(this.httpScheme)
.host(this.serverAddress)
.port(this.serverPort)
.toUriString();
final ClientCredentials sebClientCredentials = this.sebClientConfigDAO
.getSebClientCredentials(config.getModelId())
.getOrThrow();
final ClientCredentials sebClientCredentials = this.sebClientConfigDAO
.getSebClientCredentials(config.getModelId())
.getOrThrow();
final CharSequence encryptionPassword = this.sebClientConfigDAO
.getConfigPasswortCipher(config.getModelId())
.getOrThrow();
final CharSequence encryptionPassword = this.sebClientConfigDAO
.getConfigPasswortCipher(config.getModelId())
.getOrThrow();
final CharSequence plainClientId = this.clientCredentialService
.getPlainClientId(sebClientCredentials);
final CharSequence plainClientSecret = this.clientCredentialService
.getPlainClientSecret(sebClientCredentials);
final CharSequence plainClientId = this.clientCredentialService
.getPlainClientId(sebClientCredentials);
final CharSequence plainClientSecret = this.clientCredentialService
.getPlainClientSecret(sebClientCredentials);
final String plainTextConfig = String.format(
SEB_CLIENT_CONFIG_EXAMPLE_XML,
serverURL,
String.valueOf(config.institutionId),
plainClientId,
plainClientSecret,
API.OAUTH_TOKEN_ENDPOINT,
this.sebClientAPIEndpoint + API.EXAM_API_HANDSHAKE_ENDPOINT,
this.sebClientAPIEndpoint + API.EXAM_API_CONFIGURATION_REQUEST_ENDPOINT,
this.sebClientAPIEndpoint + API.EXAM_API_PING_ENDPOINT,
this.sebClientAPIEndpoint + API.EXAM_API_EVENT_ENDPOINT);
final String plainTextConfig = String.format(
SEB_CLIENT_CONFIG_EXAMPLE_XML,
serverURL,
String.valueOf(config.institutionId),
plainClientId,
plainClientSecret,
API.OAUTH_TOKEN_ENDPOINT,
this.sebClientAPIEndpoint + API.EXAM_API_HANDSHAKE_ENDPOINT,
this.sebClientAPIEndpoint + API.EXAM_API_CONFIGURATION_REQUEST_ENDPOINT,
this.sebClientAPIEndpoint + API.EXAM_API_PING_ENDPOINT,
this.sebClientAPIEndpoint + API.EXAM_API_EVENT_ENDPOINT);
PipedOutputStream pOut = null;
PipedInputStream pIn = null;
try {
// zip the plain text
final InputStream plainIn = IOUtils.toInputStream(plainTextConfig, "UTF-8");
pOut = new PipedOutputStream();
pIn = new PipedInputStream(pOut);
this.zipService.write(pOut, plainIn);
if (encryptionPassword != null) {
log.debug("Try to encrypt seb client configuration with password based encryption");
final CharSequence encryptionPasswordPlaintext = this.clientCredentialService
.decrypt(encryptionPassword);
final ByteBuffer encryptedConfig = this.sebConfigEncryptionService.encryptWithPassword(
plainTextConfig,
Strategy.PASSWORD_PSWD,
encryptionPasswordPlaintext)
.getOrThrow();
return new ByteArrayInputStream(Utils.toByteArray(encryptedConfig));
passwordEncryption(output, encryptionPassword, pIn);
} else {
log.debug("Serve plain text seb configuration with specified header");
final ByteBuffer encryptedConfig = this.sebConfigEncryptionService.plainText(plainTextConfig)
.getOrThrow();
return new ByteArrayInputStream(Utils.toByteArray(encryptedConfig));
noEncryption(output, plainTextConfig);
}
});
} catch (final Exception e) {
log.error("Error while zip and encrypt seb client config stream: ", e);
try {
if (pIn != null)
pIn.close();
} catch (final IOException e1) {
log.error("Failed to close PipedInputStream: ", e1);
}
try {
if (pOut != null)
pOut.close();
} catch (final IOException e1) {
log.error("Failed to close PipedOutputStream: ", e1);
}
}
}
private void noEncryption(
final OutputStream output,
final String plainTextConfig) throws IOException {
log.debug("Serve plain text seb configuration with specified header");
final ByteBuffer encryptedConfig = this.sebConfigEncryptionService.plainText(plainTextConfig)
.getOrThrow();
IOUtils.copyLarge(
new ByteArrayInputStream(Utils.toByteArray(encryptedConfig)),
output);
}
private void passwordEncryption(
final OutputStream output,
final CharSequence encryptionPassword,
final InputStream input) {
if (log.isDebugEnabled()) {
log.debug("*** Seb client configuration with password based encryption");
}
final CharSequence encryptionPasswordPlaintext = this.clientCredentialService
.decrypt(encryptionPassword);
this.sebConfigEncryptionService.streamEncryption(
output,
input,
Strategy.PASSWORD_PSWD,
encryptionPasswordPlaintext);
if (log.isDebugEnabled()) {
log.debug("*** Finished Seb client configuration with password based encryption");
}
}
}

View file

@ -8,6 +8,11 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.ByteBuffer;
import java.security.cert.Certificate;
import java.util.Arrays;
@ -17,6 +22,7 @@ import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -66,20 +72,49 @@ public final class SebConfigEncryptionServiceImpl implements SebConfigEncryption
}
@Override
public Result<ByteBuffer> encryptWithPassword(
final CharSequence plainTextConfig,
public void streamEncryption(
final OutputStream output,
final InputStream input,
final Strategy strategy,
final CharSequence password) {
if (log.isDebugEnabled()) {
log.debug("Password encryption with strategy: {}", strategy);
}
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
return getEncryptor(strategy)
.flatMap(encryptor -> encryptor.encrypt(
plainTextConfig,
EncryptionContext.contextOf(strategy, password)))
.map(bb -> addHeader(bb, strategy));
if (log.isDebugEnabled()) {
log.debug("Password encryption with strategy: {}", strategy);
}
pout.write(strategy.header);
getEncryptor(strategy)
.getOrThrow()
.encrypt(pout,
input,
EncryptionContext.contextOf(strategy, password));
IOUtils.copyLarge(pin, output);
pin.close();
pout.flush();
pout.close();
} catch (final IOException e) {
log.error("Error while stream encrypted data: ", e);
} finally {
try {
pin.close();
} catch (final IOException e1) {
log.error("Failed to close PipedInputStream: ", e1);
}
try {
pout.close();
} catch (final IOException e1) {
log.error("Failed to close PipedOutputStream: ", e1);
}
}
}
@Override

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService;
@Lazy
@Component
@WebServiceProfile
public class ZipServiceImpl implements ZipService {
private static final Logger log = LoggerFactory.getLogger(ZipServiceImpl.class);
@Override
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
public void write(final OutputStream out, final InputStream in) {
if (log.isDebugEnabled()) {
log.debug("*** Start streaming asynchronous zipping of SEB exam configuration data");
}
GZIPOutputStream zipOutputStream = null;
try {
zipOutputStream = new GZIPOutputStream(out);
IOUtils.copyLarge(in, zipOutputStream);
in.close();
zipOutputStream.flush();
zipOutputStream.close();
} catch (final IOException e) {
log.error("Error while streaming data to zipped output: ", e);
} finally {
try {
if (zipOutputStream != null)
zipOutputStream.close();
} catch (final IOException e) {
log.error("Failed to close ZipOutputStream: ", e);
}
if (log.isDebugEnabled()) {
log.debug("*** Finish streaming asynchronous zipping of SEB exam configuration data");
}
}
}
@Override
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
public void read(final OutputStream out, final InputStream in) {
// TODO Auto-generated method stub
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@EnableAsync
@Configuration
@WebServiceProfile
public class ControllerConfig implements WebMvcConfigurer {
@Override
public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
configurer.setTaskExecutor(threadPoolTaskExecutor());
configurer.setDefaultTimeout(30_000);
}
public AsyncTaskExecutor threadPoolTaskExecutor() {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.setThreadNamePrefix("mvc-");
executor.initialize();
return executor;
}
}

View file

@ -8,21 +8,21 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.io.InputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.mybatis.dynamic.sql.SqlTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.EnableAsync;
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.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
@ -45,9 +45,12 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationSe
@WebServiceProfile
@RestController
@EnableAsync
@RequestMapping("/${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_CONFIG_ENDPOINT)
public class SebClientConfigController extends ActivatableEntityController<SebClientConfig, SebClientConfig> {
private static final Logger log = LoggerFactory.getLogger(SebClientConfigController.class);
private final SebClientConfigService sebClientConfigService;
public SebClientConfigController(
@ -73,22 +76,16 @@ public class SebClientConfigController extends ActivatableEntityController<SebCl
path = API.SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void downloadSEBConfig(
@PathVariable final String modelId,
final HttpServletResponse response) throws Exception {
public ResponseEntity<StreamingResponseBody> downloadSEBConfig(
@PathVariable final String modelId) {
this.entityDAO.byModelId(modelId)
.map(this.authorization::checkWrite);
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setStatus(HttpStatus.OK.value());
final StreamingResponseBody stream = out -> this.sebClientConfigService
.exportSebClientConfiguration(out, modelId);
final InputStream sebConfigFileIn = this.sebClientConfigService
.exportSebClientConfiguration(modelId)
.getOrThrow();
IOUtils.copyLarge(sebConfigFileIn, response.getOutputStream());
response.flushBuffer();
return new ResponseEntity<>(stream, HttpStatus.OK);
}
@Override

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl;
import static org.junit.Assert.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.apache.commons.io.IOUtils;
import org.cryptonode.jncryptor.AES256JNCryptor;
import org.cryptonode.jncryptor.AES256JNCryptorOutputStream;
import org.cryptonode.jncryptor.JNCryptor;
import org.junit.Test;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionContext;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService.Strategy;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebConfigEncryptionServiceImpl.EncryptionContext;
public class PasswordEncryptorTest {
@Test
public void conversionTest() throws IOException {
final String text = "ojnjbiboijnlkncokdnvoiwjife";
final byte[] byteArray = Utils.toByteArray(text);
final byte[] otherByteArray = new byte[byteArray.length];
final InputStream inputStream = IOUtils.toInputStream(text, "UTF-8");
inputStream.read(otherByteArray);
assertTrue(Arrays.equals(byteArray, otherByteArray));
}
@Test
public void testUsingPassword() throws Exception {
final String config = "<TestConfig></TestConfig>";
final byte[] plaintext = Utils.toByteArray(config);//getRandomBytes(127);
final String password = "Testing1234";
final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
final AES256JNCryptorOutputStream cryptorStream = new AES256JNCryptorOutputStream(
byteStream, password.toCharArray());
cryptorStream.write(plaintext);
cryptorStream.close();
final byte[] encrypted = byteStream.toByteArray();
final JNCryptor cryptor = new AES256JNCryptor();
final byte[] result = cryptor.decryptData(encrypted, password.toCharArray());
assertArrayEquals(plaintext, result);
}
@Test
public void test1() {
final JNCryptor jnCryptor = new AES256JNCryptor();
jnCryptor.setPBKDFIterations(10000);
final PasswordEncryptor encryptor = new PasswordEncryptor(jnCryptor);
final String config = "<TestConfig></TestConfig>";
final String pwd = "password";
final SebConfigEncryptionContext context = EncryptionContext.contextOf(
Strategy.PASSWORD_PWCC,
pwd);
final Result<ByteBuffer> encrypt = encryptor.encrypt(config, context);
assertFalse(encrypt.hasError());
final ByteBuffer cipher = encrypt.getOrThrow();
final byte[] byteArray = Utils.toByteArray(cipher);
final Result<ByteBuffer> decrypt = encryptor.decrypt(cipher, context);
assertFalse(decrypt.hasError());
final String decryptedConfig = Utils.toString(decrypt.getOrThrow());
assertEquals(config, decryptedConfig);
}
@Test
public void test2() throws IOException {
final JNCryptor jnCryptor = new AES256JNCryptor();
jnCryptor.setPBKDFIterations(10000);
final PasswordEncryptor encryptor = new PasswordEncryptor(jnCryptor);
final String config = "<TestConfig></TestConfig>";
final String pwd = "password";
final ByteArrayOutputStream out = new ByteArrayOutputStream(512);
final SebConfigEncryptionContext context = EncryptionContext.contextOf(
Strategy.PASSWORD_PWCC,
pwd);
encryptor.encrypt(
out,
IOUtils.toInputStream(config, "UTF-8"),
context);
final byte[] byteArray = out.toByteArray();
final Result<ByteBuffer> decrypt = encryptor.decrypt(
ByteBuffer.wrap(byteArray),
context);
assertFalse(decrypt.hasError());
final ByteBuffer buffer = decrypt.getOrThrow();
buffer.rewind();
final String decryptedConfig = Utils.toString(buffer);
assertEquals(config, decryptedConfig);
}
}

View file

@ -10,18 +10,21 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl;
import static org.junit.Assert.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.cryptonode.jncryptor.AES256JNCryptor;
import org.cryptonode.jncryptor.JNCryptor;
import org.junit.Test;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService.Strategy;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigCryptor;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService.Strategy;
public class SebConfigEncryptionServiceImplTest {
@ -42,19 +45,24 @@ public class SebConfigEncryptionServiceImplTest {
}
@Test
public void testPasswordEncryption() {
public void testPasswordEncryption() throws IOException {
final SebConfigEncryptionServiceImpl sebConfigEncryptionServiceImpl = sebConfigEncryptionServiceImpl();
final String config = "<TestConfig></TestConfig>";
final String pwd = "password";
final Result<ByteBuffer> plainText = sebConfigEncryptionServiceImpl.encryptWithPassword(
config,
final ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
sebConfigEncryptionServiceImpl.streamEncryption(
out,
IOUtils.toInputStream(config, "UTF-8"),
Strategy.PASSWORD_PWCC,
pwd);
assertFalse(plainText.hasError());
final ByteBuffer cipher = plainText.get();
final byte[] byteArray = out.toByteArray();
//assertFalse(plainText.hasError());
final ByteBuffer cipher = ByteBuffer.wrap(byteArray);
assertTrue(Utils.toString(cipher).startsWith(Utils.toString(Strategy.PASSWORD_PWCC.header)));
final Result<ByteBuffer> decrypt = sebConfigEncryptionServiceImpl.decrypt(cipher, () -> pwd, null);

View file

@ -19,3 +19,5 @@ sebserver.webservice.api.exam.accessTokenValiditySeconds=1800
sebserver.webservice.api.exam.refreshTokenValiditySeconds=-1
sebserver.webservice.internalSecret=TO_SET
sebserver.webservice.api.redirect.unauthorized=none
management.endpoints.web.base-path=/actuator