added Config-Key export (not yet correct Config-Key)

This commit is contained in:
anhefti 2019-07-29 16:15:37 +02:00
parent 58c8b12ebe
commit d84ec117c1
23 changed files with 509 additions and 91 deletions

View file

@ -108,6 +108,7 @@ public final class API {
public static final String CONFIGURATION_NODE_ENDPOINT = "/configuration_node";
public static final String CONFIGURATION_FOLLOWUP_PATH_SEGMENT = "/followup";
public static final String CONFIGURATION_CONFIG_KEY_PATH_SEGMENT = "/configkey";
public static final String CONFIGURATION_ENDPOINT = "/configuration";
public static final String CONFIGURATION_SAVE_TO_HISTORY_PATH_SEGMENT = "/save_to_history";
public static final String CONFIGURATION_UNDO_PATH_SEGMENT = "/undo";

View file

@ -0,0 +1,64 @@
/*
* 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.gbl.model.sebconfig;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public final class ConfigKey {
public static final String ATTR_KEY = "key";
@JsonProperty(ATTR_KEY)
public final String key;
@JsonCreator
public ConfigKey(@JsonProperty(ATTR_KEY) final String key) {
this.key = key;
}
public String getKey() {
return this.key;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.key == null) ? 0 : this.key.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final ConfigKey other = (ConfigKey) obj;
if (this.key == null) {
if (other.key != null)
return false;
} else if (!this.key.equals(other.key))
return false;
return true;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("ConfigKey [key=");
builder.append(this.key);
builder.append("]");
return builder.toString();
}
}

View file

@ -385,7 +385,13 @@ public class ExamForm implements TemplateComposer {
indicatorTable::getSelection,
this::deleteSelectedIndicator,
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent() && editable);
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent() && editable)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_GET_CONFIG_KEY)
.withEntityKey(entityKey)
.withExec(SebExamConfigPropForm.getConfigKeyFunction(this.pageService))
.publishIf(() -> userGrantCheck.r());
;
}
}

View file

@ -8,9 +8,13 @@
package ch.ethz.seb.sebserver.gui.content;
import java.util.function.Function;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.UrlLauncher;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
@ -20,6 +24,7 @@ import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
@ -32,15 +37,19 @@ import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
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.TemplateComposer;
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.DownloadService;
import ch.ethz.seb.sebserver.gui.service.remote.SebExamConfigDownload;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ExportConfigKey;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
@Lazy
@Component
@ -60,6 +69,9 @@ public class SebExamConfigPropForm implements TemplateComposer {
private static final LocTextKey FORM_STATUS_TEXT_KEY =
new LocTextKey("sebserver.examconfig.form.status");
private static final LocTextKey CONFIG_KEY_TITLE_TEXT_KEY =
new LocTextKey("sebserver.examconfig.form.config-key.title");
private final PageService pageService;
private final RestService restService;
private final CurrentUser currentUser;
@ -183,6 +195,11 @@ public class SebExamConfigPropForm implements TemplateComposer {
})
.publishIf(() -> readGrant && isReadonly)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_GET_CONFIG_KEY)
.withEntityKey(entityKey)
.withExec(SebExamConfigPropForm.getConfigKeyFunction(this.pageService))
.publishIf(() -> readGrant && isReadonly)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_SAVE)
.withEntityKey(entityKey)
.withExec(formHandle::processFormSave)
@ -198,4 +215,37 @@ public class SebExamConfigPropForm implements TemplateComposer {
}
public static Function<PageAction, PageAction> getConfigKeyFunction(final PageService pageService) {
final RestService restService = pageService.getResourceService().getRestService();
return action -> {
final ConfigKey configKey = restService.getBuilder(ExportConfigKey.class)
.withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId)
.call()
.getOrThrow();
final WidgetFactory widgetFactory = pageService.getWidgetFactory();
final ModalInputDialog<Void> dialog = new ModalInputDialog<>(
action.pageContext().getParent().getShell(),
widgetFactory);
dialog.open(
CONFIG_KEY_TITLE_TEXT_KEY,
action.pageContext(),
pc -> {
final Composite content = widgetFactory.defaultPageLayout(
pc.getParent());
widgetFactory.labelLocalized(
content,
CustomVariant.TEXT_H3,
CONFIG_KEY_TITLE_TEXT_KEY);
final Text text = new Text(content, SWT.NONE);
text.setEditable(false);
text.setText(configKey.key);
});
return action;
};
}
}

View file

@ -167,6 +167,10 @@ public class SebExamConfigSettingsForm implements TemplateComposer {
.withSuccess(KEY_UNDO_SUCCESS)
.publishIf(() -> examConfigGrant.iw())
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
.withEntityKey(entityKey)
.publish()
;
} catch (final Exception e) {

View file

@ -177,7 +177,6 @@ public enum ActionDefinition {
QUIZ_DISCOVERY_SHOW_DETAILS(
new LocTextKey("sebserver.quizdiscovery.action.details"),
ImageIcon.SHOW,
null,
ActionCategory.QUIZ_LIST),
QUIZ_DISCOVERY_EXAM_IMPORT(
new LocTextKey("sebserver.quizdiscovery.action.import"),
@ -340,6 +339,12 @@ public enum ActionDefinition {
ImageIcon.SHOW,
PageStateDefinition.SEB_EXAM_CONFIG_VIEW,
ActionCategory.SEB_EXAM_CONFIG_LIST),
SEB_EXAM_CONFIG_VIEW_PROP(
new LocTextKey("sebserver.examconfig.action.view"),
ImageIcon.SHOW,
PageStateDefinition.SEB_EXAM_CONFIG_VIEW,
ActionCategory.FORM),
SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST(
new LocTextKey("sebserver.examconfig.action.list.modify.properties"),
ImageIcon.EDIT,
@ -370,6 +375,10 @@ public enum ActionDefinition {
ImageIcon.EXPORT,
PageStateDefinition.SEB_EXAM_CONFIG_VIEW,
ActionCategory.FORM),
SEB_EXAM_CONFIG_GET_CONFIG_KEY(
new LocTextKey("sebserver.examconfig.action.get-config-key"),
ImageIcon.SECURE,
ActionCategory.FORM),
SEB_EXAM_CONFIG_MODIFY_FROM_LIST(
new LocTextKey("sebserver.examconfig.action.list.modify"),
@ -381,12 +390,12 @@ public enum ActionDefinition {
new LocTextKey("sebserver.examconfig.action.saveToHistory"),
ImageIcon.SAVE,
PageStateDefinition.SEB_EXAM_CONFIG_EDIT,
ActionCategory.SEB_EXAM_CONFIG_LIST),
ActionCategory.FORM),
SEB_EXAM_CONFIG_UNDO(
new LocTextKey("sebserver.examconfig.action.undo"),
ImageIcon.UNDO,
PageStateDefinition.SEB_EXAM_CONFIG_EDIT,
ActionCategory.SEB_EXAM_CONFIG_LIST),
ActionCategory.FORM),
RUNNING_EXAM_VIEW_LIST(
new LocTextKey("sebserver.monitoring.action.list"),
@ -422,6 +431,14 @@ public enum ActionDefinition {
this(title, icon, targetState, ActionCategory.VARIA);
}
private ActionDefinition(
final LocTextKey title,
final ImageIcon icon,
final ActionCategory category) {
this(title, icon, null, category);
}
private ActionDefinition(
final LocTextKey title,
final ImageIcon icon,

View file

@ -0,0 +1,42 @@
/*
* 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.remote.webservice.api.seb.examconfig;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
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.ConfigKey;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class ExportConfigKey extends RestCall<ConfigKey> {
public ExportConfigKey() {
super(new TypeKey<>(
CallType.UNDEFINED,
EntityType.CONFIGURATION_NODE,
new TypeReference<ConfigKey>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.CONFIGURATION_NODE_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.CONFIGURATION_CONFIG_KEY_PATH_SEGMENT);
}
}

View file

@ -34,8 +34,8 @@ public class ExportPlainXML extends AbstractExportCall {
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.CONFIGURATION_NODE_ENDPOINT
+ API.CONFIGURATION_PLAIN_XML_DOWNLOAD_PATH_SEGMENT
+ API.MODEL_ID_VAR_PATH_SEGMENT);
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.CONFIGURATION_PLAIN_XML_DOWNLOAD_PATH_SEGMENT);
}
}

View file

@ -78,6 +78,7 @@ public class WidgetFactory {
NO("no.png"),
SAVE("save.png"),
EXPORT("export.png"),
SECURE("secure.png"),
NEW("new.png"),
DELETE("delete.png"),
SEARCH("lens.png"),

View file

@ -0,0 +1,14 @@
/*
* 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;
public enum ConfigurationFormat {
XML,
JSON
}

View file

@ -13,6 +13,7 @@ import java.io.OutputStream;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.gbl.util.Result;
/** The base interface and service for all SEB Exam Configuration related functionality. */
public interface SebExamConfigService {
@ -40,13 +41,25 @@ public interface SebExamConfigService {
* @param configurationNodeId the identifier of the ConfigurationNode to export */
void exportPlainXML(OutputStream out, Long institutionId, Long configurationNodeId);
/** Used to export a specified SEB Exam Configuration as plain JSON
* This exports the values of the follow-up configuration defined by a given
* ConfigurationNode (configurationNodeId) and sorts the attributes recording to
* the SEB configuration JSON specification to create a Config-Key as
* described here: https://www.safeexambrowser.org/developer/seb-config-key.html
*
* @param out The output stream to write the plain JSON text to.
* @param institutionId The identifier of the institution of the requesting user
* @param configurationNodeId the identifier of the ConfigurationNode to export */
void exportPlainJSON(OutputStream out, Long institutionId, Long configurationNodeId);
/** Used to export the default SEB Exam Configuration for a given exam identifier.
* either with encryption if defined or as plain text within the SEB Configuration format
* as described here: https://www.safeexambrowser.org/developer/seb-file-format.html
*
* @param out The output stream to write the export data to
* @param institutionId The identifier of the institution of the requesting user
* @param examId the exam identifier */
* @param examId the exam identifier
* @return The configuration node identifier (PK) */
default Long exportForExam(final OutputStream out, final Long institutionId, final Long examId) {
return exportForExam(out, institutionId, examId, null);
}
@ -58,10 +71,11 @@ public interface SebExamConfigService {
* @param out The output stream to write the export data to
* @param institutionId The identifier of the institution of the requesting user
* @param examId the exam identifier
* @param userId the user identifier if a specific user based configuration shall be exported */
* @param userId the user identifier if a specific user based configuration shall be exported
* @return The configuration node identifier (PK) */
Long exportForExam(OutputStream out, Long institutionId, Long examId, String userId);
/** TODO */
String generateConfigKey(Long configurationNodeId);
Result<String> generateConfigKey(Long institutionId, Long configurationNodeId);
}

View file

@ -11,9 +11,11 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -35,6 +37,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverter;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverterService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationFormat;
@Lazy
@Component
@ -49,6 +52,9 @@ public class ExamConfigIO {
private static final byte[] XML_PLIST_END_UTF_8 = Utils.toByteArray(Constants.XML_PLIST_END);
private static final byte[] XML_DICT_START_UTF_8 = Utils.toByteArray(Constants.XML_DICT_START);
private static final byte[] XML_DICT_END_UTF_8 = Utils.toByteArray(Constants.XML_DICT_END);
private static final byte[] JSON_START = Utils.toByteArray("{");
private static final byte[] JSON_END = Utils.toByteArray("}");
private static final byte[] JSON_SEPARATOR = Utils.toByteArray(Constants.LIST_SEPARATOR);
private final ConfigurationAttributeDAO configurationAttributeDAO;
private final ConfigurationValueDAO configurationValueDAO;
@ -68,7 +74,8 @@ public class ExamConfigIO {
}
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
void exportPlainXML(
void exportPlain(
final ConfigurationFormat exportFormat,
final OutputStream out,
final Long institutionId,
final Long configurationNodeId) {
@ -82,6 +89,7 @@ public class ExamConfigIO {
.getOrThrow()
.stream()
.flatMap(this::convertAttribute)
.filter(exportFormatBasedAttributeFilter(exportFormat))
.sorted()
.collect(Collectors.toList());
@ -94,25 +102,39 @@ public class ExamConfigIO {
getConfigurationValueSupplier(institutionId, configurationId);
try {
// write headers
out.write(XML_VERSION_HEADER_UTF_8);
out.write(XML_DOCTYPE_HEADER_UTF_8);
// plist open
out.write(XML_PLIST_START_V1_UTF_8);
out.write(XML_DICT_START_UTF_8);
writeHeader(exportFormat, out);
// write attributes
for (final ConfigurationAttribute attribute : sortedAttributes) {
this.attributeValueConverterService.getAttributeValueConverter(attribute).convertToXML(
out,
attribute,
configurationValueSupplier);
final Iterator<ConfigurationAttribute> iterator = sortedAttributes.iterator();
while (iterator.hasNext()) {
final ConfigurationAttribute attribute = iterator.next();
final AttributeValueConverter attributeValueConverter =
this.attributeValueConverterService.getAttributeValueConverter(attribute);
switch (exportFormat) {
case XML: {
attributeValueConverter.convertToXML(
out,
attribute,
configurationValueSupplier);
break;
}
case JSON: {
attributeValueConverter.convertToJSON(
out,
attribute,
configurationValueSupplier);
if (iterator.hasNext()) {
out.write(JSON_SEPARATOR);
}
break;
}
}
}
// plist close
out.write(XML_DICT_END_UTF_8);
out.write(XML_PLIST_END_UTF_8);
writeFooter(exportFormat, out);
out.flush();
if (log.isDebugEnabled()) {
@ -131,6 +153,49 @@ public class ExamConfigIO {
}
}
private Predicate<ConfigurationAttribute> exportFormatBasedAttributeFilter(final ConfigurationFormat format) {
// Filter originatorVersion according to: https://www.safeexambrowser.org/developer/seb-config-key.html
return attr -> !("originatorVersion".equals(attr.getName()) && format == ConfigurationFormat.JSON);
}
private void writeFooter(
final ConfigurationFormat exportFormat,
final OutputStream out) throws IOException {
if (exportFormat == ConfigurationFormat.XML) {
// plist close
out.write(XML_DICT_END_UTF_8);
out.write(XML_PLIST_END_UTF_8);
} else {
out.write(JSON_END);
}
}
private void writeHeader(
final ConfigurationFormat exportFormat,
final OutputStream out) throws IOException {
if (exportFormat == ConfigurationFormat.XML) {
writeXMLHeaderInformation(out);
} else {
writeJSONHeaderInformation(out);
}
}
private void writeJSONHeaderInformation(final OutputStream out) throws IOException {
out.write(JSON_START);
}
private void writeXMLHeaderInformation(final OutputStream out) throws IOException {
// write headers
out.write(XML_VERSION_HEADER_UTF_8);
out.write(XML_DOCTYPE_HEADER_UTF_8);
// plist open
out.write(XML_PLIST_START_V1_UTF_8);
out.write(XML_DICT_START_UTF_8);
}
private Stream<ConfigurationAttribute> convertAttribute(final ConfigurationAttribute attr) {
final AttributeValueConverter attributeValueConverter =
this.attributeValueConverterService.getAttributeValueConverter(attr);

View file

@ -13,7 +13,11 @@ import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
@ -21,6 +25,8 @@ import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
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.FieldValidationException;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues;
@ -29,6 +35,7 @@ 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.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.SebExamConfigService;
@ -77,8 +84,28 @@ public class SebExamConfigServiceImpl implements SebExamConfigService {
@Override
public void validate(final ConfigurationTableValues tableValue) throws FieldValidationException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("TODO");
final List<APIMessage> errors = tableValue.values.stream()
.map(tv -> new ConfigurationValue(
null,
tableValue.institutionId,
tableValue.configurationId,
tv.attributeId,
tv.listIndex,
tv.value))
.flatMap(cv -> {
try {
validate(cv);
return Stream.empty();
} catch (final FieldValidationException fve) {
return Stream.of(fve);
}
})
.map(fve -> fve.apiMessage)
.collect(Collectors.toList());
if (!errors.isEmpty()) {
throw new APIMessageException(errors);
}
}
@Override
@ -87,48 +114,16 @@ public class SebExamConfigServiceImpl implements SebExamConfigService {
final Long institutionId,
final Long configurationNodeId) {
if (log.isDebugEnabled()) {
log.debug("Start to stream plain text SEB clonfiguration data");
}
this.exportPlain(ConfigurationFormat.XML, out, institutionId, configurationNodeId);
}
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
this.examConfigIO.exportPlainXML(
pout,
institutionId,
configurationNodeId);
IOUtils.copyLarge(pin, out);
pout.flush();
pout.close();
pin.close();
} catch (final IOException e) {
log.error("Error while stream plain text SEB clonfiguration data: ", e);
} finally {
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);
}
if (log.isDebugEnabled()) {
log.debug("Finished to stream plain text SEB clonfiguration data");
}
}
@Override
public void exportPlainJSON(
final OutputStream out,
final Long institutionId,
final Long configurationNodeId) {
this.exportPlain(ConfigurationFormat.JSON, out, institutionId, configurationNodeId);
}
public Result<Long> getDefaultConfigurationIdForExam(final Long examId) {
@ -160,12 +155,125 @@ public class SebExamConfigServiceImpl implements SebExamConfigService {
}
@Override
public String generateConfigKey(final Long configurationNodeId) {
public Result<String> generateConfigKey(
final Long institutionId,
final Long configurationNodeId) {
//DigestUtils.sha1Hex(data)
if (log.isDebugEnabled()) {
log.debug("Start to stream plain JSON SEB clonfiguration data for Config-Key generation");
}
// TODO https://www.safeexambrowser.org/developer/seb-config-key.html
throw new UnsupportedOperationException("TODO");
if (log.isTraceEnabled()) {
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain(
ConfigurationFormat.JSON,
pout,
institutionId,
configurationNodeId);
final String json = IOUtils.toString(pin, "UTF-8");
log.trace("SEB Configuration JSON to create Config-Key: {}", json);
} catch (final Exception e) {
log.error("Failed to trace SEB Configuration JSON: ", e);
}
}
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain(
ConfigurationFormat.JSON,
pout,
institutionId,
configurationNodeId);
final String configKey = DigestUtils.sha256Hex(pin);
pout.flush();
pout.close();
pin.close();
return Result.of(configKey);
} catch (final IOException e) {
log.error("Error while stream plain JSON SEB clonfiguration data for Config-Key generation: ", e);
return Result.ofError(e);
} finally {
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);
}
if (log.isDebugEnabled()) {
log.debug("Finished to stream plain JSON SEB clonfiguration data for Config-Key generation");
}
}
}
private void exportPlain(
final ConfigurationFormat exportFormat,
final OutputStream out,
final Long institutionId,
final Long configurationNodeId) {
if (log.isDebugEnabled()) {
log.debug("Start to stream plain text SEB clonfiguration data");
}
PipedOutputStream pout = null;
PipedInputStream pin = null;
try {
pout = new PipedOutputStream();
pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain(
exportFormat,
pout,
institutionId,
configurationNodeId);
IOUtils.copyLarge(pin, out);
pout.flush();
pout.close();
pin.close();
} catch (final IOException e) {
log.error("Error while stream plain text SEB clonfiguration data: ", e);
} finally {
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);
}
if (log.isDebugEnabled()) {
log.debug("Finished to stream plain text SEB clonfiguration data");
}
}
}
}

View file

@ -10,9 +10,6 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.converter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
@ -35,16 +32,24 @@ public class IntegerConverter implements AttributeValueConverter {
private static final Logger log = LoggerFactory.getLogger(IntegerConverter.class);
public static final Set<AttributeType> SUPPORTED_TYPES = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList(
AttributeType.INTEGER,
AttributeType.SLIDER,
AttributeType.SINGLE_SELECTION,
AttributeType.RADIO_SELECTION)));
public static final Set<String> SUPPORTED_ATTR_NAMES = Utils.immutableSetOf(
"taskBarHeight",
"allowedDisplaysMaxNumber");
public static final Set<AttributeType> SUPPORTED_TYPES = Utils.immutableSetOf(
AttributeType.INTEGER,
AttributeType.SLIDER,
AttributeType.SINGLE_SELECTION,
AttributeType.RADIO_SELECTION);
private static final String XML_TEMPLATE = "<key>%s</key><integer>%s</integer>";
private static final String JSON_TEMPLATE = "\"%s\":%s";
@Override
public Set<String> names() {
return SUPPORTED_ATTR_NAMES;
}
@Override
public Set<AttributeType> types() {
return SUPPORTED_TYPES;

View file

@ -44,7 +44,7 @@ public class StringConverter implements AttributeValueConverter {
private static final String XML_TEMPLATE_EMPTY = "<key>%s</key><string />";
private static final String JSON_TEMPLATE = "\"%s\":\"%s\"";
private static final String JSON_TEMPLATE_EMPTY = "\"%s\":";
private static final String JSON_TEMPLATE_EMPTY = "\"%s\":\"\"";
@Override
public Set<AttributeType> types() {

View file

@ -78,7 +78,7 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
}
@ExceptionHandler(OAuth2Exception.class)
public ResponseEntity<Object> handleBeanValidationException(
public ResponseEntity<Object> handleOAuth2Exception(
final OAuth2Exception ex,
final WebRequest request) {

View file

@ -22,6 +22,7 @@ import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBo
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@ -84,20 +85,42 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Configuration getFollowup(@PathVariable final Long configNodeId) {
public Configuration getFollowup(@PathVariable final Long modelId) {
this.entityDAO
.byPK(configNodeId)
.byPK(modelId)
.flatMap(this::checkModifyAccess)
.getOrThrow();
return this.configurationDAO
.getFollowupConfiguration(configNodeId)
.getFollowupConfiguration(modelId)
.getOrThrow();
}
@RequestMapping(
path = API.CONFIGURATION_PLAIN_XML_DOWNLOAD_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT,
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_CONFIG_KEY_PATH_SEGMENT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ConfigKey getConfigKey(
@PathVariable final Long modelId,
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
this.entityDAO.byPK(modelId)
.flatMap(this.authorization::checkRead);
final String configKey = this.sebExamConfigService
.generateConfigKey(institutionId, modelId)
.getOrThrow();
return new ConfigKey(configKey);
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT + API.CONFIGURATION_PLAIN_XML_DOWNLOAD_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<StreamingResponseBody> downloadPlainXMLConfig(
@ -108,7 +131,7 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
this.entityDAO.byPK(modelId)
.map(this.authorization::checkRead);
.flatMap(this.authorization::checkRead);
final StreamingResponseBody stream = out -> this.sebExamConfigService
.exportPlainXML(out, institutionId, modelId);

View file

@ -215,7 +215,7 @@ INSERT IGNORE INTO configuration_attribute VALUES
(259, 'SOCKSPassword', 'TEXT_FIELD', 220, null, null, 'groupId=socks,createDefaultValue=true', null),
(260, 'RTSPEnable', 'CHECKBOX', 220, null, null, 'groupId=rtsp,createDefaultValue=true', 'false'),
(261, 'RTSPProxy', 'TEXT_FIELD', 220, null, null, 'groupId=rtsp,createDefaultValue=true', null),
(262, 'RTSPPort', 'INTEGER', 220, null, null, 'groupId=rtsp,createDefaultValue=true', '1080'),
(262, 'RTSPPort', 'INTEGER', 220, null, null, 'groupId=rtsp,createDefaultValue=true', '554'),
(263, 'RTSPRequiresPassword', 'CHECKBOX', 220, null, null, 'groupId=rtsp,createDefaultValue=true', 'false'),
(264, 'RTSPUsername', 'TEXT_FIELD', 220, null, null, 'groupId=rtsp,createDefaultValue=true', null),
(265, 'RTSPPassword', 'TEXT_FIELD', 220, null, null, 'groupId=rtsp,createDefaultValue=true', null),
@ -228,7 +228,7 @@ INSERT IGNORE INTO configuration_attribute VALUES
(304, 'enablePrivateClipboard', 'CHECKBOX', null, null, null, null, 'true'),
(305, 'enableLogging', 'CHECKBOX', null, null, null, null, 'false'),
(306, 'logDirectoryWin', 'TEXT_FIELD', null, null, null, null, ''),
(307, 'logDirectoryOSX', 'TEXT_FIELD', null, null, null, null, '~/Documents'),
(307, 'logDirectoryOSX', 'TEXT_FIELD', null, null, null, null, 'NSTemporaryDirectory'),
(308, 'minMacOSVersion', 'SINGLE_SELECTION', null, '0,1,2,3,4,5,6,7', null, null, '0'),
(309, 'enableAppSwitcherCheck', 'CHECKBOX', null, null, null, null, 'true'),
(310, 'forceAppFolderInstall', 'CHECKBOX', null, null, null, null, 'true'),

View file

@ -33,6 +33,7 @@
<Logger name="org.springframework.security.oauth2" level="INFO" additivity="true" />
<Logger name="ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebClientConfigServiceImpl" level="DEBUG" additivity="true" />
<Logger name="ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebExamConfigServiceImpl" level="TRACE" additivity="true" />
</springProfile>

View file

@ -392,6 +392,7 @@ sebserver.examconfig.action.list.new=New Exam Configuration
sebserver.examconfig.action.list.view=View Configuration
sebserver.examconfig.action.list.modify=Edit Settings
sebserver.examconfig.action.list.modify.properties=Edit Configuration
sebserver.examconfig.action.view=View Configuration
sebserver.examconfig.action.modify=Edit Settings
sebserver.examconfig.action.modify.properties=Edit Configuration
sebserver.examconfig.action.save=Save
@ -400,12 +401,14 @@ sebserver.examconfig.action.saveToHistory.success=Successfully saved in history
sebserver.examconfig.action.undo=Undo
sebserver.examconfig.action.undo.success=Successfully reverted to last saved state
sebserver.examconfig.action.export.plainxml=Export XML
sebserver.examconfig.action.get-config-key=Export Config-Key
sebserver.examconfig.form.title.new=New Exam Configuration
sebserver.examconfig.form.title=Exam Configuration
sebserver.examconfig.form.name=Name
sebserver.examconfig.form.description=Description
sebserver.examconfig.form.status=Status
sebserver.examconfig.form.config-key.title=Config Key
sebserver.examconfig.status.CONSTRUCTION=Under Construction
sebserver.examconfig.status.READY_TO_USE=Ready To Use

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

View file

@ -183,7 +183,7 @@ INSERT IGNORE INTO configuration_attribute VALUES
(259, 'SOCKSPassword', 'TEXT_FIELD', 220, null, null, 'groupId=socks,createDefaultValue=true', null),
(260, 'RTSPEnable', 'CHECKBOX', 220, null, null, 'groupId=rtsp,createDefaultValue=true', 'false'),
(261, 'RTSPProxy', 'TEXT_FIELD', 220, null, null, 'groupId=rtsp,createDefaultValue=true', null),
(262, 'RTSPPort', 'INTEGER', 220, null, null, 'groupId=rtsp,createDefaultValue=true', '1080'),
(262, 'RTSPPort', 'INTEGER', 220, null, null, 'groupId=rtsp,createDefaultValue=true', '554'),
(263, 'RTSPRequiresPassword', 'CHECKBOX', 220, null, null, 'groupId=rtsp,createDefaultValue=true', 'false'),
(264, 'RTSPUsername', 'TEXT_FIELD', 220, null, null, 'groupId=rtsp,createDefaultValue=true', null),
(265, 'RTSPPassword', 'TEXT_FIELD', 220, null, null, 'groupId=rtsp,createDefaultValue=true', null),
@ -196,7 +196,7 @@ INSERT IGNORE INTO configuration_attribute VALUES
(304, 'enablePrivateClipboard', 'CHECKBOX', null, null, null, null, 'true'),
(305, 'enableLogging', 'CHECKBOX', null, null, null, null, 'false'),
(306, 'logDirectoryWin', 'TEXT_FIELD', null, null, null, null, ''),
(307, 'logDirectoryOSX', 'TEXT_FIELD', null, null, null, null, '~/Documents'),
(307, 'logDirectoryOSX', 'TEXT_FIELD', null, null, null, null, 'NSTemporaryDirectory'),
(308, 'minMacOSVersion', 'SINGLE_SELECTION', null, '0,1,2,3,4,5,6,7', null, null, '0'),
(309, 'enableAppSwitcherCheck', 'CHECKBOX', null, null, null, null, 'true'),
(310, 'forceAppFolderInstall', 'CHECKBOX', null, null, null, null, 'true'),