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
This commit is contained in:
parent
14334f0d7e
commit
6c8aa7b12c
12 changed files with 334 additions and 35 deletions
|
@ -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<PageAction, PageAction> exportFunction() {
|
||||||
|
|
||||||
|
return action -> {
|
||||||
|
|
||||||
|
final ModalInputDialog<FormHandle<?>> dialog =
|
||||||
|
new ModalInputDialog<FormHandle<?>>(
|
||||||
|
action.pageContext().getParent().getShell(),
|
||||||
|
this.pageService.getWidgetFactory())
|
||||||
|
.setLargeDialogWidth();
|
||||||
|
|
||||||
|
final CreationFormContext creationFormContext = new CreationFormContext(
|
||||||
|
this.pageService,
|
||||||
|
action.pageContext());
|
||||||
|
|
||||||
|
final Predicate<FormHandle<?>> 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<FormHandle<?>> {
|
||||||
|
|
||||||
|
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<FormHandle<?>> compose(final Composite parent) {
|
||||||
|
|
||||||
|
final List<Tuple<String>> 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<ConfigCreationInfo> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -166,11 +166,13 @@ public class ExamForm implements TemplateComposer {
|
||||||
private final String downloadFileName;
|
private final String downloadFileName;
|
||||||
private final WidgetFactory widgetFactory;
|
private final WidgetFactory widgetFactory;
|
||||||
private final RestService restService;
|
private final RestService restService;
|
||||||
|
private final ExamCreateClientConfigPopup examCreateClientConfigPopup;
|
||||||
|
|
||||||
protected ExamForm(
|
protected ExamForm(
|
||||||
final PageService pageService,
|
final PageService pageService,
|
||||||
final ResourceService resourceService,
|
final ResourceService resourceService,
|
||||||
final DownloadService downloadService,
|
final DownloadService downloadService,
|
||||||
|
final ExamCreateClientConfigPopup examCreateClientConfigPopup,
|
||||||
@Value("${sebserver.gui.seb.exam.config.download.filename}") final String downloadFileName) {
|
@Value("${sebserver.gui.seb.exam.config.download.filename}") final String downloadFileName) {
|
||||||
|
|
||||||
this.pageService = pageService;
|
this.pageService = pageService;
|
||||||
|
@ -179,6 +181,7 @@ public class ExamForm implements TemplateComposer {
|
||||||
this.downloadFileName = downloadFileName;
|
this.downloadFileName = downloadFileName;
|
||||||
this.widgetFactory = pageService.getWidgetFactory();
|
this.widgetFactory = pageService.getWidgetFactory();
|
||||||
this.restService = this.resourceService.getRestService();
|
this.restService = this.resourceService.getRestService();
|
||||||
|
this.examCreateClientConfigPopup = examCreateClientConfigPopup;
|
||||||
|
|
||||||
this.consistencyMessageMapping = new HashMap<>();
|
this.consistencyMessageMapping = new HashMap<>();
|
||||||
this.consistencyMessageMapping.put(
|
this.consistencyMessageMapping.put(
|
||||||
|
@ -245,6 +248,7 @@ public class ExamForm implements TemplateComposer {
|
||||||
final boolean modifyGrant = userGrantCheck.m();
|
final boolean modifyGrant = userGrantCheck.m();
|
||||||
final ExamStatus examStatus = exam.getStatus();
|
final ExamStatus examStatus = exam.getStatus();
|
||||||
final boolean isExamRunning = examStatus == ExamStatus.RUNNING;
|
final boolean isExamRunning = examStatus == ExamStatus.RUNNING;
|
||||||
|
final boolean writeGrant = userGrantCheck.w();
|
||||||
final boolean editable = examStatus == ExamStatus.UP_COMING
|
final boolean editable = examStatus == ExamStatus.UP_COMING
|
||||||
|| examStatus == ExamStatus.RUNNING
|
|| examStatus == ExamStatus.RUNNING
|
||||||
&& currentUser.get().hasRole(UserRole.EXAM_ADMIN);
|
&& currentUser.get().hasRole(UserRole.EXAM_ADMIN);
|
||||||
|
@ -391,6 +395,11 @@ public class ExamForm implements TemplateComposer {
|
||||||
.withExec(this.cancelModifyFunction())
|
.withExec(this.cancelModifyFunction())
|
||||||
.publishIf(() -> !readonly)
|
.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)
|
.newAction(ActionDefinition.EXAM_MODIFY_SEB_RESTRICTION_DETAILS)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
.withExec(ExamSEBRestrictionSettings.settingsFunction(this.pageService))
|
.withExec(ExamSEBRestrictionSettings.settingsFunction(this.pageService))
|
||||||
|
|
|
@ -306,6 +306,7 @@ public class SEBClientConfigForm implements TemplateComposer {
|
||||||
FALLBACK_ATTRIBUTES::contains,
|
FALLBACK_ATTRIBUTES::contains,
|
||||||
ffa -> ffa.setVisible(BooleanUtils.isTrue(clientConfig.fallback)));
|
ffa -> ffa.setVisible(BooleanUtils.isTrue(clientConfig.fallback)));
|
||||||
|
|
||||||
|
if (!isReadonly) {
|
||||||
formHandle.getForm().getFieldInput(SEBClientConfig.ATTR_FALLBACK)
|
formHandle.getForm().getFieldInput(SEBClientConfig.ATTR_FALLBACK)
|
||||||
.addListener(SWT.Selection, event -> formHandle.process(
|
.addListener(SWT.Selection, event -> formHandle.process(
|
||||||
FALLBACK_ATTRIBUTES::contains,
|
FALLBACK_ATTRIBUTES::contains,
|
||||||
|
@ -317,6 +318,7 @@ public class SEBClientConfigForm implements TemplateComposer {
|
||||||
ffa.setStringValue(StringUtils.EMPTY);
|
ffa.setStringValue(StringUtils.EMPTY);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
|
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
|
||||||
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
||||||
|
|
|
@ -353,6 +353,11 @@ public enum ActionDefinition {
|
||||||
ImageIcon.CANCEL,
|
ImageIcon.CANCEL,
|
||||||
PageStateDefinitionImpl.EXAM_VIEW,
|
PageStateDefinitionImpl.EXAM_VIEW,
|
||||||
ActionCategory.FORM),
|
ActionCategory.FORM),
|
||||||
|
EXAM_SEB_CLIENT_CONFIG_EXPORT(
|
||||||
|
new LocTextKey("sebserver.exam.action.createClientToStartExam"),
|
||||||
|
ImageIcon.EXPORT,
|
||||||
|
PageStateDefinitionImpl.EXAM_VIEW,
|
||||||
|
ActionCategory.FORM),
|
||||||
|
|
||||||
SEB_CLIENT_CONFIG_LIST(
|
SEB_CLIENT_CONFIG_LIST(
|
||||||
new LocTextKey("sebserver.clientconfig.list.title"),
|
new LocTextKey("sebserver.clientconfig.list.title"),
|
||||||
|
|
|
@ -13,6 +13,7 @@ import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
@ -20,7 +21,9 @@ import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
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.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.RestService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.ExportClientConfig;
|
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.ExportClientConfig;
|
||||||
|
|
||||||
|
@ -45,8 +48,15 @@ public class SEBClientConfigDownload extends AbstractDownloadServiceHandler {
|
||||||
@Override
|
@Override
|
||||||
protected void webserviceCall(final String modelId, final String parentModelId, final OutputStream downloadOut) {
|
protected void webserviceCall(final String modelId, final String parentModelId, final OutputStream downloadOut) {
|
||||||
|
|
||||||
final InputStream input = this.restService.getBuilder(ExportClientConfig.class)
|
final RestCall<InputStream>.RestCallBuilder restCallBuilder = this.restService
|
||||||
.withURIVariable(API.PARAM_MODEL_ID, modelId)
|
.getBuilder(ExportClientConfig.class)
|
||||||
|
.withURIVariable(API.PARAM_MODEL_ID, modelId);
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(parentModelId)) {
|
||||||
|
restCallBuilder.withQueryParam(EXAM.ATTR_ID, parentModelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
final InputStream input = restCallBuilder
|
||||||
.call()
|
.call()
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
|
|
|
@ -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<SEBClientConfig> {
|
||||||
|
|
||||||
|
public GetClientConfigs() {
|
||||||
|
super(
|
||||||
|
GetClientConfigPage.class,
|
||||||
|
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||||
|
new TypeReference<List<SEBClientConfig>>() {
|
||||||
|
},
|
||||||
|
API.SEB_CLIENT_CONFIG_ENDPOINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
|
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.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.ClientCredentialService;
|
||||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
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.Domain;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig;
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig;
|
||||||
|
@ -245,18 +245,20 @@ public class SEBClientConfigDAOImpl implements SEBClientConfigDAO {
|
||||||
|
|
||||||
checkUniqueName(sebClientConfig);
|
checkUniqueName(sebClientConfig);
|
||||||
|
|
||||||
|
final SebClientConfigRecord record =
|
||||||
|
this.sebClientConfigRecordMapper.selectByPrimaryKey(sebClientConfig.id);
|
||||||
|
|
||||||
final SebClientConfigRecord newRecord = new SebClientConfigRecord(
|
final SebClientConfigRecord newRecord = new SebClientConfigRecord(
|
||||||
sebClientConfig.id,
|
sebClientConfig.id,
|
||||||
null,
|
record.getInstitutionId(),
|
||||||
sebClientConfig.name,
|
sebClientConfig.name,
|
||||||
null,
|
record.getDate(),
|
||||||
null,
|
record.getClientName(),
|
||||||
null,
|
record.getClientSecret(),
|
||||||
getEncryptionPassword(sebClientConfig),
|
getEncryptionPassword(sebClientConfig),
|
||||||
null);
|
record.getActive());
|
||||||
|
|
||||||
this.sebClientConfigRecordMapper
|
this.sebClientConfigRecordMapper.updateByPrimaryKey(newRecord);
|
||||||
.updateByPrimaryKeySelective(newRecord);
|
|
||||||
|
|
||||||
saveAdditionalAttributes(sebClientConfig, newRecord.getId());
|
saveAdditionalAttributes(sebClientConfig, newRecord.getId());
|
||||||
|
|
||||||
|
|
|
@ -45,10 +45,12 @@ public interface ClientConfigService {
|
||||||
* as described here: https://www.safeexambrowser.org/developer/seb-file-format.html
|
* as described here: https://www.safeexambrowser.org/developer/seb-file-format.html
|
||||||
*
|
*
|
||||||
* @param out OutputStream to write the export to
|
* @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(
|
void exportSEBClientConfiguration(
|
||||||
OutputStream out,
|
OutputStream out,
|
||||||
final String modelId);
|
final String modelId,
|
||||||
|
final Long examId);
|
||||||
|
|
||||||
/** Get the ClientDetails for given client name that identifies a SEBClientConfiguration entry.
|
/** Get the ClientDetails for given client name that identifies a SEBClientConfiguration entry.
|
||||||
*
|
*
|
||||||
|
|
|
@ -13,6 +13,8 @@ import java.io.OutputStream;
|
||||||
import java.io.PipedInputStream;
|
import java.io.PipedInputStream;
|
||||||
import java.io.PipedOutputStream;
|
import java.io.PipedOutputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -21,6 +23,7 @@ import java.util.UUID;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
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.client.ClientCredentials;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
|
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;
|
||||||
|
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig.ConfigPurpose;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
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 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 =
|
private static final String SEB_CLIENT_CONFIG_TEMPLATE_XML =
|
||||||
" <dict>%n" +
|
" <dict>%n" +
|
||||||
" <key>sebMode</key>%n" +
|
" <key>sebMode</key>%n" +
|
||||||
|
@ -80,7 +85,7 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
||||||
" <key>sebServerConfiguration</key>%n" +
|
" <key>sebServerConfiguration</key>%n" +
|
||||||
" <dict>%n" +
|
" <dict>%n" +
|
||||||
" <key>institution</key>%n" +
|
" <key>institution</key>%n" +
|
||||||
" <string>%s</string>%n" +
|
" <string>%s</string>%n%s" +
|
||||||
" <key>clientName</key>%n" +
|
" <key>clientName</key>%n" +
|
||||||
" <string>%s</string>%n" +
|
" <string>%s</string>%n" +
|
||||||
" <key>clientSecret</key>%n" +
|
" <key>clientSecret</key>%n" +
|
||||||
|
@ -186,16 +191,17 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
||||||
@Override
|
@Override
|
||||||
public void exportSEBClientConfiguration(
|
public void exportSEBClientConfiguration(
|
||||||
final OutputStream output,
|
final OutputStream output,
|
||||||
final String modelId) {
|
final String modelId,
|
||||||
|
final Long examId) {
|
||||||
|
|
||||||
final SEBClientConfig config = this.sebClientConfigDAO
|
final SEBClientConfig config = this.sebClientConfigDAO
|
||||||
.byModelId(modelId).getOrThrow();
|
.byModelId(modelId).getOrThrow();
|
||||||
|
|
||||||
final CharSequence encryptionPassword = this.sebClientConfigDAO
|
final CharSequence encryptionPassword = this.sebClientConfigDAO
|
||||||
.getConfigPasswordCipher(config.getModelId())
|
.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;
|
PipedOutputStream pOut = null;
|
||||||
PipedInputStream pIn = null;
|
PipedInputStream pIn = null;
|
||||||
|
@ -223,7 +229,7 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
||||||
|
|
||||||
if (encryptionPassword != null) {
|
if (encryptionPassword != null) {
|
||||||
// encrypt zipped plain text and add header
|
// encrypt zipped plain text and add header
|
||||||
passwordEncryption(zipOut, encryptionPassword, pIn);
|
passwordEncryption(zipOut, encryptionPassword, config.getConfigPurpose(), pIn);
|
||||||
} else {
|
} else {
|
||||||
// just add plain text header
|
// just add plain text header
|
||||||
this.sebConfigEncryptionService.streamEncrypted(
|
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 = "";
|
String fallbackAddition = "";
|
||||||
if (BooleanUtils.isTrue(config.fallback)) {
|
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
|
final ClientCredentials sebClientCredentials = this.sebClientConfigDAO
|
||||||
.getSEBClientCredentials(config.getModelId())
|
.getSEBClientCredentials(config.getModelId())
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
@ -305,6 +319,7 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
||||||
fallbackAddition,
|
fallbackAddition,
|
||||||
this.webserviceInfo.getExternalServerURL(),
|
this.webserviceInfo.getExternalServerURL(),
|
||||||
config.institutionId,
|
config.institutionId,
|
||||||
|
examIdAddition,
|
||||||
plainClientId,
|
plainClientId,
|
||||||
plainClientSecret,
|
plainClientSecret,
|
||||||
this.webserviceInfo.getDiscoveryEndpoint());
|
this.webserviceInfo.getDiscoveryEndpoint());
|
||||||
|
@ -375,22 +390,50 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
||||||
private void passwordEncryption(
|
private void passwordEncryption(
|
||||||
final OutputStream output,
|
final OutputStream output,
|
||||||
final CharSequence encryptionPassword,
|
final CharSequence encryptionPassword,
|
||||||
|
final ConfigPurpose configPurpose,
|
||||||
final InputStream input) {
|
final InputStream input) {
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("*** SEB client configuration with password based encryption");
|
log.debug("*** SEB client configuration with password based encryption");
|
||||||
}
|
}
|
||||||
|
|
||||||
final CharSequence encryptionPasswordPlaintext = (encryptionPassword == StringUtils.EMPTY)
|
final CharSequence plainTextPassword = getPlainTextPassword(
|
||||||
? StringUtils.EMPTY
|
encryptionPassword,
|
||||||
: this.clientCredentialService.decrypt(encryptionPassword);
|
configPurpose);
|
||||||
|
|
||||||
this.sebConfigEncryptionService.streamEncrypted(
|
this.sebConfigEncryptionService.streamEncrypted(
|
||||||
output,
|
output,
|
||||||
input,
|
input,
|
||||||
EncryptionContext.contextOf(
|
EncryptionContext.contextOf(
|
||||||
(encryptionPassword == StringUtils.EMPTY) ? Strategy.PASSWORD_PWCC : Strategy.PASSWORD_PSWD,
|
(configPurpose == ConfigPurpose.CONFIGURE_CLIENT)
|
||||||
encryptionPasswordPlaintext));
|
? 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.
|
/** Get a encoded clientSecret for the SEBClientConfiguration with specified clientId/clientName.
|
||||||
|
|
|
@ -69,6 +69,7 @@ public class PasswordEncryptor implements SEBConfigCryptor {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
final CharSequence password = context.getPassword();
|
final CharSequence password = context.getPassword();
|
||||||
|
|
||||||
if (password.length() == 0) {
|
if (password.length() == 0) {
|
||||||
encryptOutput = new AES256JNCryptorOutputStreamEmptyPwdSupport(
|
encryptOutput = new AES256JNCryptorOutputStreamEmptyPwdSupport(
|
||||||
output,
|
output,
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.springframework.validation.FieldError;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
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.APIMessage;
|
||||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
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.sebconfig.SEBClientConfig;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
|
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
|
||||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||||
|
@ -83,6 +85,7 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
|
||||||
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
|
||||||
public void downloadSEBConfig(
|
public void downloadSEBConfig(
|
||||||
@PathVariable final String modelId,
|
@PathVariable final String modelId,
|
||||||
|
@RequestParam(name = EXAM.ATTR_ID, required = false) final Long examId,
|
||||||
final HttpServletResponse response) throws IOException {
|
final HttpServletResponse response) throws IOException {
|
||||||
|
|
||||||
this.entityDAO.byModelId(modelId)
|
this.entityDAO.byModelId(modelId)
|
||||||
|
@ -98,7 +101,8 @@ public class SEBClientConfigController extends ActivatableEntityController<SEBCl
|
||||||
|
|
||||||
this.sebClientConfigService.exportSEBClientConfiguration(
|
this.sebClientConfigService.exportSEBClientConfiguration(
|
||||||
pout,
|
pout,
|
||||||
modelId);
|
modelId,
|
||||||
|
examId);
|
||||||
|
|
||||||
IOUtils.copyLarge(pin, outputStream);
|
IOUtils.copyLarge(pin, outputStream);
|
||||||
|
|
||||||
|
|
|
@ -423,6 +423,7 @@ sebserver.exam.action.deactivate=Deactivate Exam
|
||||||
sebserver.exam.action.sebrestriction.enable=Apply SEB Lock
|
sebserver.exam.action.sebrestriction.enable=Apply SEB Lock
|
||||||
sebserver.exam.action.sebrestriction.disable=Release SEB Lock
|
sebserver.exam.action.sebrestriction.disable=Release SEB Lock
|
||||||
sebserver.exam.action.sebrestriction.details=SEB Restriction Details
|
sebserver.exam.action.sebrestriction.details=SEB Restriction Details
|
||||||
|
sebserver.exam.action.createClientToStartExam=Export Start Exam Config
|
||||||
|
|
||||||
sebserver.exam.info.pleaseSelect=At first please select an Exam from the list
|
sebserver.exam.info.pleaseSelect=At first please select an Exam from the list
|
||||||
|
|
||||||
|
@ -450,6 +451,12 @@ sebserver.exam.form.type.tooltip=The type of the exam.<br/><br/>This has only de
|
||||||
sebserver.exam.form.supporter=Exam Supporter
|
sebserver.exam.form.supporter=Exam Supporter
|
||||||
sebserver.exam.form.supporter.tooltip=A list of users that are allowed to support this exam<br/><br/>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.<br/>A filtered choice will drop down. Select a specific username in the dropdown list to add the user to the list.<br/>To remove a user from the list, just double-click the username on the list.
|
sebserver.exam.form.supporter.tooltip=A list of users that are allowed to support this exam<br/><br/>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.<br/>A filtered choice will drop down. Select a specific username in the dropdown list to add the user to the list.<br/>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<br/>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".<br/><br/>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=SEB Restriction Details
|
||||||
sebserver.exam.form.sebrestriction.title.subtitle=
|
sebserver.exam.form.sebrestriction.title.subtitle=
|
||||||
sebserver.exam.form.sebrestriction.info=Info
|
sebserver.exam.form.sebrestriction.info=Info
|
||||||
|
|
Loading…
Reference in a new issue