fallback attributes and new password attribute handling
This commit is contained in:
parent
26561288c9
commit
7238369550
52 changed files with 11407 additions and 10482 deletions
|
@ -120,6 +120,8 @@ public final class Constants {
|
|||
public static final int GZIP_ID2 = 0x8B;
|
||||
public static final int GZIP_CM = 8;
|
||||
|
||||
public static final String SHA_256 = "SHA-256";
|
||||
|
||||
public static final RGB WHITE_RGB = new RGB(255, 255, 255);
|
||||
public static final RGB BLACK_RGB = new RGB(0, 0, 0);
|
||||
|
||||
|
|
|
@ -67,6 +67,15 @@ public class POSTMapper {
|
|||
return Long.parseLong(value);
|
||||
}
|
||||
|
||||
public Short getShort(final String name) {
|
||||
final String value = this.params.getFirst(name);
|
||||
if (StringUtils.isBlank(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Short.parseShort(value);
|
||||
}
|
||||
|
||||
public Integer getInteger(final String name) {
|
||||
final String value = this.params.getFirst(name);
|
||||
if (value == null) {
|
||||
|
|
|
@ -30,11 +30,25 @@ import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
|||
|
||||
public final class SebClientConfig implements GrantEntity, Activatable {
|
||||
|
||||
public static final String ATTR_FALLBACK_START_URL = "fallback_start_url";
|
||||
public static final String ATTR_CONFIRM_ENCRYPT_SECRET = "confirm_encrypt_secret";
|
||||
public static final String ATTR_CONFIG_PURPOSE = "sebConfigPurpose";
|
||||
public static final String ATTR_FALLBACK = "sebServerFallback ";
|
||||
public static final String ATTR_FALLBACK_START_URL = "startURL";
|
||||
public static final String ATTR_FALLBACK_TIMEOUT = "sebServerFallbackTimeout";
|
||||
public static final String ATTR_FALLBACK_ATTEMPTS = "sebServerFallbackAttempts";
|
||||
public static final String ATTR_FALLBACK_ATTEMPT_INTERVAL = "sebServerFallbackAttemptInterval";
|
||||
public static final String ATTR_FALLBACK_PASSWORD = "sebServerFallbackPasswordHash";
|
||||
public static final String ATTR_FALLBACK_PASSWORD_CONFIRM = "sebServerFallbackPasswordHashConfirm";
|
||||
public static final String ATTR_QUIT_PASSWORD = "hashedQuitPassword";
|
||||
public static final String ATTR_QUIT_PASSWORD_CONFIRM = "hashedQuitPasswordConfirm";
|
||||
public static final String ATTR_ENCRYPT_SECRET_CONFIRM = "confirm_encrypt_secret";
|
||||
|
||||
public static final String FILTER_ATTR_CREATION_DATE = "creation_date";
|
||||
|
||||
public enum ConfigPurpose {
|
||||
START_EXAM,
|
||||
CONFIGURE_CLIENT
|
||||
}
|
||||
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ID)
|
||||
public final Long id;
|
||||
|
||||
|
@ -47,18 +61,46 @@ public final class SebClientConfig implements GrantEntity, Activatable {
|
|||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_NAME)
|
||||
public final String name;
|
||||
|
||||
@NotNull(message = "clientconfig:sebConfigPurpose:notNull")
|
||||
@JsonProperty(ATTR_CONFIG_PURPOSE)
|
||||
public final ConfigPurpose configPurpose;
|
||||
|
||||
@JsonProperty(ATTR_FALLBACK)
|
||||
public final Boolean fallback;
|
||||
|
||||
@JsonProperty(ATTR_FALLBACK_START_URL)
|
||||
@URL(message = "clientconfig:fallback_start_url:invalidURL")
|
||||
@URL(message = "clientconfig:startURL:invalidURL")
|
||||
public final String fallbackStartURL;
|
||||
|
||||
@JsonProperty(ATTR_FALLBACK_TIMEOUT)
|
||||
public final Long fallbackTimeout;
|
||||
|
||||
@JsonProperty(ATTR_FALLBACK_ATTEMPTS)
|
||||
public final Short fallbackAttempts;
|
||||
|
||||
@JsonProperty(ATTR_FALLBACK_ATTEMPT_INTERVAL)
|
||||
public final Short fallbackAttemptInterval;
|
||||
|
||||
@JsonProperty(ATTR_FALLBACK_PASSWORD)
|
||||
public final CharSequence fallbackPassword;
|
||||
|
||||
@JsonProperty(ATTR_FALLBACK_PASSWORD_CONFIRM)
|
||||
public final CharSequence fallbackPasswordConfirm;
|
||||
|
||||
@JsonProperty(ATTR_QUIT_PASSWORD)
|
||||
public final CharSequence quitPassword;
|
||||
|
||||
@JsonProperty(ATTR_QUIT_PASSWORD_CONFIRM)
|
||||
public final CharSequence quitPasswordConfirm;
|
||||
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_DATE)
|
||||
public final DateTime date;
|
||||
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET)
|
||||
public final CharSequence encryptSecret;
|
||||
|
||||
@JsonProperty(ATTR_CONFIRM_ENCRYPT_SECRET)
|
||||
public final CharSequence confirmEncryptSecret;
|
||||
@JsonProperty(ATTR_ENCRYPT_SECRET_CONFIRM)
|
||||
public final CharSequence encryptSecretConfirm;
|
||||
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE)
|
||||
public final Boolean active;
|
||||
|
@ -68,19 +110,37 @@ public final class SebClientConfig implements GrantEntity, Activatable {
|
|||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ID) final Long id,
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID) final Long institutionId,
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_NAME) final String name,
|
||||
@JsonProperty(ATTR_CONFIG_PURPOSE) final ConfigPurpose configPurpose,
|
||||
@JsonProperty(ATTR_FALLBACK) final Boolean fallback,
|
||||
@JsonProperty(ATTR_FALLBACK_START_URL) final String fallbackStartURL,
|
||||
@JsonProperty(ATTR_FALLBACK_TIMEOUT) final Long fallbackTimeout,
|
||||
@JsonProperty(ATTR_FALLBACK_ATTEMPTS) final Short fallbackAttempts,
|
||||
@JsonProperty(ATTR_FALLBACK_ATTEMPT_INTERVAL) final Short fallbackAttemptInterval,
|
||||
@JsonProperty(ATTR_FALLBACK_PASSWORD) final CharSequence fallbackPassword,
|
||||
@JsonProperty(ATTR_FALLBACK_PASSWORD_CONFIRM) final CharSequence fallbackPasswordConfirm,
|
||||
@JsonProperty(ATTR_QUIT_PASSWORD) final CharSequence quitPassword,
|
||||
@JsonProperty(ATTR_QUIT_PASSWORD_CONFIRM) final CharSequence quitPasswordConfirm,
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_DATE) final DateTime date,
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET) final CharSequence encryptSecret,
|
||||
@JsonProperty(ATTR_CONFIRM_ENCRYPT_SECRET) final CharSequence confirmEncryptSecret,
|
||||
@JsonProperty(ATTR_ENCRYPT_SECRET_CONFIRM) final CharSequence encryptSecretConfirm,
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) final Boolean active) {
|
||||
|
||||
this.id = id;
|
||||
this.institutionId = institutionId;
|
||||
this.name = name;
|
||||
this.configPurpose = configPurpose;
|
||||
this.fallback = fallback;
|
||||
this.fallbackStartURL = fallbackStartURL;
|
||||
this.fallbackTimeout = fallbackTimeout;
|
||||
this.fallbackAttempts = fallbackAttempts;
|
||||
this.fallbackAttemptInterval = fallbackAttemptInterval;
|
||||
this.fallbackPassword = fallbackPassword;
|
||||
this.fallbackPasswordConfirm = fallbackPasswordConfirm;
|
||||
this.quitPassword = quitPassword;
|
||||
this.quitPasswordConfirm = quitPasswordConfirm;
|
||||
this.date = date;
|
||||
this.encryptSecret = encryptSecret;
|
||||
this.confirmEncryptSecret = confirmEncryptSecret;
|
||||
this.encryptSecretConfirm = encryptSecretConfirm;
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
|
@ -88,10 +148,19 @@ public final class SebClientConfig implements GrantEntity, Activatable {
|
|||
this.id = null;
|
||||
this.institutionId = institutionId;
|
||||
this.name = postParams.getString(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME);
|
||||
this.configPurpose = postParams.getEnum(ATTR_CONFIG_PURPOSE, ConfigPurpose.class);
|
||||
this.fallback = postParams.getBoolean(ATTR_FALLBACK);
|
||||
this.fallbackStartURL = postParams.getString(ATTR_FALLBACK_START_URL);
|
||||
this.fallbackTimeout = postParams.getLong(ATTR_FALLBACK_TIMEOUT);
|
||||
this.fallbackAttempts = postParams.getShort(ATTR_FALLBACK_ATTEMPTS);
|
||||
this.fallbackAttemptInterval = postParams.getShort(ATTR_FALLBACK_ATTEMPT_INTERVAL);
|
||||
this.fallbackPassword = postParams.getCharSequence(ATTR_FALLBACK_PASSWORD);
|
||||
this.fallbackPasswordConfirm = postParams.getCharSequence(ATTR_FALLBACK_PASSWORD_CONFIRM);
|
||||
this.quitPassword = postParams.getCharSequence(ATTR_QUIT_PASSWORD);
|
||||
this.quitPasswordConfirm = postParams.getCharSequence(ATTR_QUIT_PASSWORD_CONFIRM);
|
||||
this.date = postParams.getDateTime(Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE);
|
||||
this.encryptSecret = postParams.getCharSequence(Domain.SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET);
|
||||
this.confirmEncryptSecret = postParams.getCharSequence(ATTR_CONFIRM_ENCRYPT_SECRET);
|
||||
this.encryptSecretConfirm = postParams.getCharSequence(ATTR_ENCRYPT_SECRET_CONFIRM);
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
|
@ -130,18 +199,55 @@ public final class SebClientConfig implements GrantEntity, Activatable {
|
|||
return this.id;
|
||||
}
|
||||
|
||||
public ConfigPurpose getConfigPurpose() {
|
||||
return configPurpose;
|
||||
}
|
||||
|
||||
public Boolean getFallback() {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public Long getFallbackTimeout() {
|
||||
return fallbackTimeout;
|
||||
}
|
||||
|
||||
public Short getFallbackAttempts() {
|
||||
return fallbackAttempts;
|
||||
}
|
||||
|
||||
public Short getFallbackAttemptInterval() {
|
||||
return fallbackAttemptInterval;
|
||||
}
|
||||
|
||||
public CharSequence getFallbackPassword() {
|
||||
return fallbackPassword;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public CharSequence getFallbackPasswordConfirm() {
|
||||
return fallbackPasswordConfirm;
|
||||
}
|
||||
|
||||
public CharSequence getQuitPassword() {
|
||||
return quitPassword;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public CharSequence getQuitPasswordConfirm() {
|
||||
return quitPasswordConfirm;
|
||||
}
|
||||
|
||||
public DateTime getDate() {
|
||||
return this.date;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public CharSequence getEncryptSecret() {
|
||||
return this.encryptSecret;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public CharSequence getConfirmEncryptSecret() {
|
||||
return this.confirmEncryptSecret;
|
||||
public CharSequence getEncryptSecretConfirm() {
|
||||
return this.encryptSecretConfirm;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
|
@ -149,47 +255,78 @@ public final class SebClientConfig implements GrantEntity, Activatable {
|
|||
return this.encryptSecret != null && this.encryptSecret.length() > 0;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean hasFallbackPassword() {
|
||||
return this.fallbackPassword != null && this.fallbackPassword.length() > 0;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean hasQuitPassword() {
|
||||
return this.quitPassword != null && this.quitPassword.length() > 0;
|
||||
}
|
||||
|
||||
public Boolean getActive() {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("SebClientConfig{");
|
||||
sb.append("id=").append(id);
|
||||
sb.append(", institutionId=").append(institutionId);
|
||||
sb.append(", name='").append(name).append('\'');
|
||||
sb.append(", configPurpose=").append(configPurpose);
|
||||
sb.append(", fallback=").append(fallback);
|
||||
sb.append(", fallbackStartURL='").append(fallbackStartURL).append('\'');
|
||||
sb.append(", fallbackTimeout=").append(fallbackTimeout);
|
||||
sb.append(", fallbackAttempts=").append(fallbackAttempts);
|
||||
sb.append(", fallbackAttemptInterval=").append(fallbackAttemptInterval);
|
||||
sb.append(", fallbackPassword=").append(fallbackPassword);
|
||||
sb.append(", fallbackPasswordConfirm=").append(fallbackPasswordConfirm);
|
||||
sb.append(", date=").append(date);
|
||||
sb.append(", encryptSecret=").append(encryptSecret);
|
||||
sb.append(", encryptSecretConfirm=").append(encryptSecretConfirm);
|
||||
sb.append(", active=").append(active);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity printSecureCopy() {
|
||||
return new SebClientConfig(
|
||||
this.id,
|
||||
this.institutionId,
|
||||
this.name,
|
||||
this.configPurpose,
|
||||
this.fallback,
|
||||
this.fallbackStartURL,
|
||||
this.fallbackTimeout,
|
||||
this.fallbackAttempts,
|
||||
this.fallbackAttemptInterval,
|
||||
Constants.EMPTY_NOTE,
|
||||
Constants.EMPTY_NOTE,
|
||||
Constants.EMPTY_NOTE,
|
||||
Constants.EMPTY_NOTE,
|
||||
this.date,
|
||||
Constants.EMPTY_NOTE,
|
||||
Constants.EMPTY_NOTE,
|
||||
this.active);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("SebClientConfig [id=");
|
||||
builder.append(this.id);
|
||||
builder.append(", institutionId=");
|
||||
builder.append(this.institutionId);
|
||||
builder.append(", name=");
|
||||
builder.append(this.name);
|
||||
builder.append(", fallbackStartURL=");
|
||||
builder.append(this.fallbackStartURL);
|
||||
builder.append(", date=");
|
||||
builder.append(this.date);
|
||||
builder.append(", active=");
|
||||
builder.append(this.active);
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static final SebClientConfig createNew(final Long institutionId) {
|
||||
public static SebClientConfig createNew(final Long institutionId) {
|
||||
return new SebClientConfig(
|
||||
null,
|
||||
institutionId,
|
||||
null,
|
||||
ConfigPurpose.START_EXAM,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
DateTime.now(DateTimeZone.UTC),
|
||||
null,
|
||||
|
|
101
src/main/java/ch/ethz/seb/sebserver/gbl/util/Cryptor.java
Normal file
101
src/main/java/ch/ethz/seb/sebserver/gbl/util/Cryptor.java
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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.gbl.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.security.crypto.encrypt.Encryptors;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
public class Cryptor {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Cryptor.class);
|
||||
|
||||
public static final String SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY = "sebserver.webservice.internalSecret";
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
public Cryptor(final Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
public CharSequence encrypt(final CharSequence text) {
|
||||
|
||||
final CharSequence secret = this.environment
|
||||
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
|
||||
|
||||
return encrypt(text, secret);
|
||||
}
|
||||
|
||||
public CharSequence decrypt(final CharSequence text) {
|
||||
|
||||
final CharSequence secret = this.environment
|
||||
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
|
||||
|
||||
return decrypt(text, secret);
|
||||
}
|
||||
|
||||
public static CharSequence encrypt(final CharSequence text, final CharSequence secret) {
|
||||
if (text == null) {
|
||||
throw new IllegalArgumentException("Text has null reference");
|
||||
}
|
||||
|
||||
if (secret == null) {
|
||||
log.warn("No internal secret supplied: skip encryption");
|
||||
return text;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
final CharSequence salt = KeyGenerators.string().generateKey();
|
||||
final CharSequence cipher = Encryptors
|
||||
.delux(secret, salt)
|
||||
.encrypt(text.toString());
|
||||
|
||||
return new StringBuilder(cipher)
|
||||
.append(salt);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to encrypt text: ", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static CharSequence decrypt(final CharSequence cipher, final CharSequence secret) {
|
||||
if (cipher == null) {
|
||||
throw new IllegalArgumentException("Cipher has null reference");
|
||||
}
|
||||
|
||||
if (secret == null) {
|
||||
log.warn("No internal secret supplied: skip decryption");
|
||||
return cipher;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
final int length = cipher.length();
|
||||
final int cipherTextLength = length - 16;
|
||||
final CharSequence salt = cipher.subSequence(cipherTextLength, length);
|
||||
final CharSequence cipherText = cipher.subSequence(0, cipherTextLength);
|
||||
|
||||
return Encryptors
|
||||
.delux(secret, salt)
|
||||
.decrypt(cipherText.toString());
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to decrypt text: ", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ import java.net.URLEncoder;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -28,6 +30,7 @@ import java.util.function.Predicate;
|
|||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.eclipse.swt.graphics.RGB;
|
||||
|
@ -478,6 +481,20 @@ public final class Utils {
|
|||
return (text == null) ? null : Constants.PERCENTAGE + text + Constants.PERCENTAGE;
|
||||
}
|
||||
|
||||
public static String hash_SHA_256_Base_16(final CharSequence chars) {
|
||||
if (chars == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final MessageDigest digest = MessageDigest.getInstance(Constants.SHA_256);
|
||||
final byte[] encodedHash = digest.digest(toByteArray(chars));
|
||||
return Hex.encodeHexString(encodedHash);
|
||||
} catch (NoSuchAlgorithmException nsae) {
|
||||
throw new RuntimeException("Failed to hash text: ", nsae);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Predicate<T> truePredicate() {
|
||||
return (Predicate<T>) TRUE_PREDICATE;
|
||||
|
|
|
@ -228,14 +228,15 @@ final class ExamToConfigBindingForm {
|
|||
resourceService.localizedExamConfigStatusName(examConfigurationMap))
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
.addField(FormBuilder.password(
|
||||
Domain.EXAM_CONFIGURATION_MAP.ATTR_ENCRYPT_SECRET,
|
||||
FORM_ENCRYPT_SECRET_TEXT_KEY)
|
||||
.asPasswordField())
|
||||
.addField(FormBuilder.text(
|
||||
FORM_ENCRYPT_SECRET_TEXT_KEY,
|
||||
examConfigurationMap.encryptSecret))
|
||||
|
||||
.addField(FormBuilder.password(
|
||||
ExamConfigurationMap.ATTR_CONFIRM_ENCRYPT_SECRET,
|
||||
FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY)
|
||||
.asPasswordField())
|
||||
FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY,
|
||||
examConfigurationMap.encryptSecret))
|
||||
|
||||
.build();
|
||||
|
||||
|
|
|
@ -8,8 +8,14 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.content;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.rap.rwt.client.service.UrlLauncher;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
@ -42,6 +48,10 @@ 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 java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
|
@ -53,15 +63,51 @@ public class SebClientConfigForm implements TemplateComposer {
|
|||
new LocTextKey("sebserver.clientconfig.form.title");
|
||||
private static final LocTextKey FORM_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.name");
|
||||
private static final LocTextKey FORM_FALLBACK_URL_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.fallback-url");
|
||||
|
||||
private static final LocTextKey FORM_DATE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.date");
|
||||
|
||||
private static final LocTextKey CLIENT_PURPOSE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.sebConfigPurpose");
|
||||
private static final LocTextKey FALLBACK_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.fallback");
|
||||
private static final LocTextKey FALLBACK_URL_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.fallback-url");
|
||||
private static final LocTextKey FALLBACK_TIMEOUT_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.sebServerFallbackTimeout");
|
||||
private static final LocTextKey FALLBACK_ATTEMPTS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.sebServerFallbackAttempts");
|
||||
private static final LocTextKey FALLBACK_ATTEMPT_INTERVAL_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.sebServerFallbackAttemptInterval");
|
||||
private static final LocTextKey FALLBACK_PASSWORD_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.sebServerFallbackPasswordHash");
|
||||
private static final LocTextKey FALLBACK_PASSWORD_CONFIRM_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.sebServerFallbackPasswordHash");
|
||||
private static final LocTextKey QUIT_PASSWORD_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.hashedQuitPassword");
|
||||
private static final LocTextKey QUIT_PASSWORD_CONFIRM_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.hashedQuitPassword.confirm");
|
||||
|
||||
private static final LocTextKey FORM_ENCRYPT_SECRET_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.encryptSecret");
|
||||
private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.encryptSecret.confirm");
|
||||
|
||||
private static final Set<String> FALLBACK_ATTRIBUTES = new HashSet<>(Arrays.asList(
|
||||
SebClientConfig.ATTR_FALLBACK_START_URL,
|
||||
SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL,
|
||||
SebClientConfig.ATTR_FALLBACK_ATTEMPTS,
|
||||
SebClientConfig.ATTR_FALLBACK_TIMEOUT,
|
||||
SebClientConfig.ATTR_FALLBACK_PASSWORD,
|
||||
SebClientConfig.ATTR_FALLBACK_PASSWORD_CONFIRM,
|
||||
SebClientConfig.ATTR_QUIT_PASSWORD,
|
||||
SebClientConfig.ATTR_QUIT_PASSWORD_CONFIRM
|
||||
));
|
||||
|
||||
private static final String FALLBACK_DEFAULT_TIME = String.valueOf(2 * Constants.MINUTE_IN_MILLIS);
|
||||
private static final String FALLBACK_DEFAULT_ATTEMPTS = String.valueOf(5);
|
||||
private static final String FALLBACK_DEFAULT_ATTEMPT_INTERVAL = String.valueOf(2 * Constants.SECOND_IN_MILLIS);
|
||||
|
||||
private final PageService pageService;
|
||||
private final RestService restService;
|
||||
private final CurrentUser currentUser;
|
||||
|
@ -131,32 +177,147 @@ public class SebClientConfigForm implements TemplateComposer {
|
|||
.putStaticValue(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID,
|
||||
String.valueOf(clientConfig.getInstitutionId()))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME,
|
||||
FORM_NAME_TEXT_KEY,
|
||||
clientConfig.name))
|
||||
.addField(FormBuilder.text(
|
||||
SebClientConfig.ATTR_FALLBACK_START_URL,
|
||||
FORM_FALLBACK_URL_TEXT_KEY,
|
||||
clientConfig.fallbackStartURL))
|
||||
|
||||
.addFieldIf(() -> !isNew,
|
||||
() -> FormBuilder.text(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE,
|
||||
FORM_DATE_TEXT_KEY,
|
||||
i18nSupport.formatDisplayDateWithTimeZone(clientConfig.date))
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME,
|
||||
FORM_NAME_TEXT_KEY,
|
||||
clientConfig.name)
|
||||
.mandatory(!isReadonly))
|
||||
|
||||
.addField(FormBuilder.singleSelection(
|
||||
SebClientConfig.ATTR_CONFIG_PURPOSE,
|
||||
CLIENT_PURPOSE_TEXT_KEY,
|
||||
clientConfig.configPurpose != null
|
||||
? clientConfig.configPurpose.name()
|
||||
: SebClientConfig.ConfigPurpose.START_EXAM.name(),
|
||||
() -> pageService.getResourceService().sebClientConfigPurposeResources())
|
||||
.mandatory(!isReadonly))
|
||||
|
||||
.withDefaultSpanInput(3)
|
||||
.addField(FormBuilder.password(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET,
|
||||
FORM_ENCRYPT_SECRET_TEXT_KEY)
|
||||
.asPasswordField())
|
||||
FORM_ENCRYPT_SECRET_TEXT_KEY,
|
||||
clientConfig.getEncryptSecret()))
|
||||
|
||||
.withDefaultSpanEmptyCell(3)
|
||||
.addFieldIf(
|
||||
() -> !isReadonly,
|
||||
() -> FormBuilder.password(
|
||||
SebClientConfig.ATTR_ENCRYPT_SECRET_CONFIRM,
|
||||
FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY,
|
||||
clientConfig.getEncryptSecret()))
|
||||
|
||||
.addField(FormBuilder.checkbox(
|
||||
SebClientConfig.ATTR_FALLBACK,
|
||||
FALLBACK_TEXT_KEY,
|
||||
clientConfig.fallback != null
|
||||
? clientConfig.fallback.toString()
|
||||
: Constants.FALSE_STRING)
|
||||
)
|
||||
|
||||
.withDefaultSpanInput(5)
|
||||
.addField(FormBuilder.text(
|
||||
SebClientConfig.ATTR_CONFIRM_ENCRYPT_SECRET,
|
||||
FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY)
|
||||
.asPasswordField())
|
||||
SebClientConfig.ATTR_FALLBACK_START_URL,
|
||||
FALLBACK_URL_TEXT_KEY,
|
||||
clientConfig.fallbackStartURL)
|
||||
.mandatory(!isReadonly))
|
||||
|
||||
.withDefaultSpanEmptyCell(1)
|
||||
.withDefaultSpanInput(2)
|
||||
.addField(FormBuilder.text(
|
||||
SebClientConfig.ATTR_FALLBACK_ATTEMPTS,
|
||||
FALLBACK_ATTEMPTS_TEXT_KEY,
|
||||
clientConfig.fallbackAttempts != null
|
||||
? String.valueOf(clientConfig.fallbackAttempts)
|
||||
: FALLBACK_DEFAULT_ATTEMPTS)
|
||||
.asNumber(this::checkNaturalNumber)
|
||||
.mandatory(!isReadonly))
|
||||
|
||||
.withDefaultSpanEmptyCell(0)
|
||||
.withEmptyCellSeparation(false)
|
||||
.withDefaultSpanLabel(1)
|
||||
.addField(FormBuilder.text(
|
||||
SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL,
|
||||
FALLBACK_ATTEMPT_INTERVAL_TEXT_KEY,
|
||||
clientConfig.fallbackAttemptInterval != null
|
||||
? String.valueOf(clientConfig.fallbackAttemptInterval)
|
||||
: FALLBACK_DEFAULT_ATTEMPT_INTERVAL)
|
||||
.asNumber(this::checkNaturalNumber)
|
||||
.mandatory(!isReadonly))
|
||||
|
||||
.withEmptyCellSeparation(true)
|
||||
.withDefaultSpanEmptyCell(1)
|
||||
.withDefaultSpanLabel(2)
|
||||
.addField(FormBuilder.text(
|
||||
SebClientConfig.ATTR_FALLBACK_TIMEOUT,
|
||||
FALLBACK_TIMEOUT_TEXT_KEY,
|
||||
clientConfig.fallbackTimeout != null
|
||||
? String.valueOf(clientConfig.fallbackTimeout)
|
||||
: FALLBACK_DEFAULT_TIME)
|
||||
.asNumber(this::checkNaturalNumber)
|
||||
.mandatory(!isReadonly))
|
||||
|
||||
.withEmptyCellSeparation(true)
|
||||
.withDefaultSpanEmptyCell(4)
|
||||
.withDefaultSpanInput(2)
|
||||
.withDefaultSpanLabel(2)
|
||||
.addField(FormBuilder.password(
|
||||
SebClientConfig.ATTR_FALLBACK_PASSWORD,
|
||||
FALLBACK_PASSWORD_TEXT_KEY,
|
||||
clientConfig.getFallbackPassword()))
|
||||
|
||||
.withEmptyCellSeparation(false)
|
||||
.withDefaultSpanLabel(1)
|
||||
.addField(FormBuilder.password(
|
||||
SebClientConfig.ATTR_QUIT_PASSWORD,
|
||||
QUIT_PASSWORD_TEXT_KEY,
|
||||
clientConfig.getQuitPassword()))
|
||||
|
||||
.withEmptyCellSeparation(true)
|
||||
.withDefaultSpanEmptyCell(1)
|
||||
.withDefaultSpanInput(2)
|
||||
.withDefaultSpanLabel(2)
|
||||
.addFieldIf(
|
||||
() -> !isReadonly,
|
||||
() -> FormBuilder.password(
|
||||
SebClientConfig.ATTR_FALLBACK_PASSWORD_CONFIRM,
|
||||
FALLBACK_PASSWORD_CONFIRM_TEXT_KEY,
|
||||
clientConfig.getFallbackPasswordConfirm()))
|
||||
|
||||
.withEmptyCellSeparation(false)
|
||||
.withDefaultSpanLabel(1)
|
||||
.addFieldIf(
|
||||
() -> !isReadonly,
|
||||
() -> FormBuilder.password(
|
||||
SebClientConfig.ATTR_QUIT_PASSWORD_CONFIRM,
|
||||
QUIT_PASSWORD_CONFIRM_TEXT_KEY,
|
||||
clientConfig.getQuitPasswordConfirm()))
|
||||
|
||||
|
||||
.buildFor((isNew)
|
||||
? this.restService.getRestCall(NewClientConfig.class)
|
||||
: this.restService.getRestCall(SaveClientConfig.class));
|
||||
|
||||
formHandle.process(
|
||||
FALLBACK_ATTRIBUTES::contains,
|
||||
ffa -> ffa.setVisible(BooleanUtils.isTrue(clientConfig.fallback))
|
||||
);
|
||||
|
||||
formHandle.getForm().getFieldInput(SebClientConfig.ATTR_FALLBACK)
|
||||
.addListener(SWT.Selection, event -> {
|
||||
formHandle.process(
|
||||
FALLBACK_ATTRIBUTES::contains,
|
||||
ffa -> ffa.setVisible(((Button) event.widget).getSelection())
|
||||
);
|
||||
});
|
||||
|
||||
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
|
||||
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
||||
|
||||
|
@ -208,4 +369,14 @@ public class SebClientConfigForm implements TemplateComposer {
|
|||
.publishIf(() -> !isReadonly);
|
||||
}
|
||||
|
||||
private void checkNaturalNumber(String value) {
|
||||
if (StringUtils.isBlank(value)) {
|
||||
return;
|
||||
}
|
||||
long num = Long.parseLong(value);
|
||||
if (num < 0) {
|
||||
throw new PageMessageException("Number must be positive");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ final class SebExamConfigImportPopup {
|
|||
try {
|
||||
final Form form = formHandle.getForm();
|
||||
final EntityKey entityKey = formHandle.getContext().getEntityKey();
|
||||
final Control fieldControl = form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
|
||||
final Control fieldControl = form.getFieldInput(API.IMPORT_FILE_ATTR_NAME);
|
||||
final PageContext context = formHandle.getContext();
|
||||
|
||||
// Ad-hoc field validation
|
||||
|
@ -270,7 +270,7 @@ final class SebExamConfigImportPopup {
|
|||
|
||||
void cancelUpload() {
|
||||
if (this.form != null) {
|
||||
final Control fieldControl = this.form.getFieldControl(API.IMPORT_FILE_ATTR_NAME);
|
||||
final Control fieldControl = this.form.getFieldInput(API.IMPORT_FILE_ATTR_NAME);
|
||||
if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
|
||||
((FileUploadSelection) fieldControl).close();
|
||||
}
|
||||
|
|
|
@ -13,20 +13,27 @@ import org.eclipse.swt.SWT;
|
|||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class CheckboxFieldBuilder extends FieldBuilder<String> {
|
||||
|
||||
protected CheckboxFieldBuilder(final String name, final LocTextKey label, final String value) {
|
||||
protected CheckboxFieldBuilder(
|
||||
final String name,
|
||||
final LocTextKey label,
|
||||
final String value) {
|
||||
|
||||
super(name, label, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void build(final FormBuilder builder) {
|
||||
final boolean readonly = builder.readonly || this.readonly;
|
||||
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
|
||||
final Button checkbox = builder.widgetFactory.buttonLocalized(
|
||||
fieldGrid,
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.eclipse.swt.SWT;
|
|||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
|
@ -113,7 +114,7 @@ public abstract class FieldBuilder<T> {
|
|||
|
||||
abstract void build(FormBuilder builder);
|
||||
|
||||
protected static Label createTitleLabel(
|
||||
protected static Control createTitleLabel(
|
||||
final Composite parent,
|
||||
final FormBuilder builder,
|
||||
final FieldBuilder<?> fieldBuilder) {
|
||||
|
@ -173,7 +174,7 @@ public abstract class FieldBuilder<T> {
|
|||
info.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
|
||||
}
|
||||
|
||||
return label;
|
||||
return infoGrid;
|
||||
}
|
||||
|
||||
public static Label labelLocalized(
|
||||
|
@ -186,7 +187,7 @@ public abstract class FieldBuilder<T> {
|
|||
return labelLocalized(widgetFactory, parent, locTextKey, defaultText, null, hspan, SWT.CENTER);
|
||||
}
|
||||
|
||||
public static final Label labelLocalized(
|
||||
public static Label labelLocalized(
|
||||
final WidgetFactory widgetFactory,
|
||||
final Composite parent,
|
||||
final LocTextKey locTextKey,
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.Collection;
|
|||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
|
@ -34,7 +35,7 @@ public class FileUploadFieldBuilder extends FieldBuilder<String> {
|
|||
|
||||
@Override
|
||||
void build(final FormBuilder builder) {
|
||||
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
|
||||
final FileUploadSelection fileUpload = builder.widgetFactory.fileUploadSelection(
|
||||
fieldGrid,
|
||||
|
|
|
@ -18,6 +18,8 @@ import java.util.function.BiConsumer;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import ch.ethz.seb.sebserver.gui.widget.PasswordInput;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
|
@ -48,6 +50,7 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
|||
|
||||
public final class Form implements FormBinding {
|
||||
|
||||
private final Cryptor cryptor;
|
||||
private final JSONMapper jsonMapper;
|
||||
private final ObjectNode objectRoot;
|
||||
|
||||
|
@ -55,8 +58,9 @@ public final class Form implements FormBinding {
|
|||
private final MultiValueMap<String, FormFieldAccessor> formFields = new LinkedMultiValueMap<>();
|
||||
private final Map<String, Set<String>> groups = new LinkedHashMap<>();
|
||||
|
||||
Form(final JSONMapper jsonMapper) {
|
||||
Form(final JSONMapper jsonMapper, final Cryptor cryptor) {
|
||||
this.jsonMapper = jsonMapper;
|
||||
this.cryptor = cryptor;
|
||||
this.objectRoot = this.jsonMapper.createObjectNode();
|
||||
}
|
||||
|
||||
|
@ -77,27 +81,23 @@ public final class Form implements FormBinding {
|
|||
appendFormUrlEncodedValue(buffer, entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
this.formFields.entrySet()
|
||||
.stream()
|
||||
.forEach(entry -> {
|
||||
entry.getValue()
|
||||
this.formFields.forEach((key, value) -> value
|
||||
.stream()
|
||||
.filter(Form::valueApplicationFilter)
|
||||
.forEach(ffa -> {
|
||||
if (ffa.listValue) {
|
||||
appendFormUrlEncodedValue(
|
||||
buffer,
|
||||
entry.getKey(),
|
||||
key,
|
||||
ffa.getStringValue());
|
||||
} else {
|
||||
appendFormUrlEncodedSingleValue(
|
||||
buffer,
|
||||
entry.getKey(),
|
||||
key,
|
||||
ffa.getStringValue(),
|
||||
false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
@ -123,45 +123,54 @@ public final class Form implements FormBinding {
|
|||
return this.formFields.containsKey(fieldName);
|
||||
}
|
||||
|
||||
Form putReadonlyField(final String name, final Label label, final Text field) {
|
||||
Form putReadonlyField(final String name, final Control label, final Text field) {
|
||||
this.formFields.add(name, createReadonlyAccessor(label, field));
|
||||
return this;
|
||||
}
|
||||
|
||||
Form putReadonlyField(final String name, final Label label, final Browser field) {
|
||||
Form putReadonlyField(final String name, final Control label, final Browser field) {
|
||||
this.formFields.add(name, createReadonlyAccessor(label, field));
|
||||
return this;
|
||||
}
|
||||
|
||||
Form putField(final String name, final Label label, final Text field, final Label errorLabel) {
|
||||
Form putField(final String name, final Control label, final Text field, final Label errorLabel) {
|
||||
this.formFields.add(name, createAccessor(label, field, errorLabel));
|
||||
return this;
|
||||
}
|
||||
|
||||
Form putField(final String name, final Label label, final Button checkbox) {
|
||||
Form putField(final String name, final Control label, final PasswordInput field, final Label errorLabel) {
|
||||
this.formFields.add(name, createAccessor(label, field, errorLabel));
|
||||
return this;
|
||||
}
|
||||
|
||||
Form putField(final String name, final Control label, final Button checkbox) {
|
||||
this.formFields.add(name, createAccessor(label, checkbox, null));
|
||||
return this;
|
||||
}
|
||||
|
||||
void putField(final String name, final Label label, final Selection field, final Label errorLabel) {
|
||||
Form putField(final String name, final Control label, final Selection field, final Label errorLabel) {
|
||||
this.formFields.add(name, createAccessor(label, field, errorLabel));
|
||||
return this;
|
||||
}
|
||||
|
||||
void putField(final String name, final Label label, final ThresholdList field, final Label errorLabel) {
|
||||
Form putField(final String name, final Control label, final ThresholdList field, final Label errorLabel) {
|
||||
this.formFields.add(name, createAccessor(label, field, errorLabel));
|
||||
return this;
|
||||
}
|
||||
|
||||
void putField(final String name, final Label label, final ImageUploadSelection imageUpload,
|
||||
Form putField(final String name, final Control label, final ImageUploadSelection imageUpload,
|
||||
final Label errorLabel) {
|
||||
final FormFieldAccessor createAccessor = createAccessor(label, imageUpload, errorLabel);
|
||||
imageUpload.setErrorHandler(createAccessor::setError);
|
||||
this.formFields.add(name, createAccessor);
|
||||
return this;
|
||||
}
|
||||
|
||||
void putField(final String name, final Label label, final FileUploadSelection fileUpload, final Label errorLabel) {
|
||||
Form putField(final String name, final Control label, final FileUploadSelection fileUpload, final Label errorLabel) {
|
||||
final FormFieldAccessor createAccessor = createAccessor(label, fileUpload, errorLabel);
|
||||
fileUpload.setErrorHandler(createAccessor::setError);
|
||||
this.formFields.add(name, createAccessor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getFieldValue(final String attributeName) {
|
||||
|
@ -173,13 +182,13 @@ public final class Form implements FormBinding {
|
|||
return fieldAccessor.getStringValue();
|
||||
}
|
||||
|
||||
public Control getFieldControl(final String attributeName) {
|
||||
public Control getFieldInput(final String attributeName) {
|
||||
final FormFieldAccessor fieldAccessor = this.formFields.getFirst(attributeName);
|
||||
if (fieldAccessor == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return fieldAccessor.control;
|
||||
return fieldAccessor.input;
|
||||
}
|
||||
|
||||
public void setFieldValue(final String attributeName, final String attributeValue) {
|
||||
|
@ -222,14 +231,14 @@ public final class Form implements FormBinding {
|
|||
|
||||
final Set<String> namesSet = this.groups.get(group);
|
||||
process(
|
||||
name -> namesSet.contains(name),
|
||||
namesSet::contains,
|
||||
ffa -> ffa.setVisible(visible));
|
||||
}
|
||||
|
||||
public void setFieldVisible(final boolean visible, final String fieldName) {
|
||||
final List<FormFieldAccessor> list = this.formFields.get(fieldName);
|
||||
if (list != null) {
|
||||
list.stream().forEach(ffa -> ffa.setVisible(visible));
|
||||
list.forEach(ffa -> ffa.setVisible(visible));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,23 +246,19 @@ public final class Form implements FormBinding {
|
|||
return this.formFields.entrySet()
|
||||
.stream()
|
||||
.flatMap(entity -> entity.getValue().stream())
|
||||
.filter(a -> a.hasError)
|
||||
.findFirst()
|
||||
.isPresent();
|
||||
.anyMatch(a -> a.hasError);
|
||||
}
|
||||
|
||||
public void clearErrors() {
|
||||
process(
|
||||
Utils.truePredicate(),
|
||||
ffa -> ffa.resetError());
|
||||
FormFieldAccessor::resetError);
|
||||
}
|
||||
|
||||
public void setFieldError(final String fieldName, final String errorMessage) {
|
||||
final List<FormFieldAccessor> list = this.formFields.get(fieldName);
|
||||
if (list != null) {
|
||||
list
|
||||
.stream()
|
||||
.forEach(ffa -> ffa.setError(errorMessage));
|
||||
list.forEach(ffa -> ffa.setError(errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,31 +296,43 @@ public final class Form implements FormBinding {
|
|||
|
||||
// following are FormFieldAccessor implementations for all field types
|
||||
//@formatter:off
|
||||
private FormFieldAccessor createReadonlyAccessor(final Label label, final Text field) {
|
||||
private FormFieldAccessor createReadonlyAccessor(final Control label, final Text field) {
|
||||
return new FormFieldAccessor(label, field, null) {
|
||||
@Override public String getStringValue() { return null; }
|
||||
@Override public void setStringValue(final String value) { field.setText( (value == null) ? StringUtils.EMPTY : value); }
|
||||
};
|
||||
}
|
||||
private FormFieldAccessor createReadonlyAccessor(final Label label, final Browser field) {
|
||||
private FormFieldAccessor createReadonlyAccessor(final Control label, final Browser field) {
|
||||
return new FormFieldAccessor(label, field, null) {
|
||||
@Override public String getStringValue() { return null; }
|
||||
@Override public void setStringValue(final String value) { field.setText( (value == null) ? StringUtils.EMPTY : value); }
|
||||
};
|
||||
}
|
||||
private FormFieldAccessor createAccessor(final Label label, final Text text, final Label errorLabel) {
|
||||
private FormFieldAccessor createAccessor(final Control label, final Text text, final Label errorLabel) {
|
||||
return new FormFieldAccessor(label, text, errorLabel) {
|
||||
@Override public String getStringValue() {return text.getText();}
|
||||
@Override public void setStringValue(final String value) {text.setText(value);}
|
||||
};
|
||||
}
|
||||
private FormFieldAccessor createAccessor(final Label label, final Button checkbox, final Label errorLabel) {
|
||||
private FormFieldAccessor createAccessor(final Control label, final PasswordInput pwdInput, final Label errorLabel) {
|
||||
return new FormFieldAccessor(label, pwdInput, errorLabel) {
|
||||
@Override public String getStringValue() {return pwdInput.getValue() != null ? pwdInput.getValue().toString() : null;}
|
||||
@Override public void setStringValue(final String value) {
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
pwdInput.setValue(cryptor.decrypt(value));
|
||||
} else {
|
||||
pwdInput.setValue(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
private FormFieldAccessor createAccessor(final Control label, final Button checkbox, final Label errorLabel) {
|
||||
return new FormFieldAccessor(label, checkbox, errorLabel) {
|
||||
@Override public String getStringValue() {return BooleanUtils.toStringTrueFalse(checkbox.getSelection());}
|
||||
@Override public void setStringValue(final String value) {checkbox.setSelection(BooleanUtils.toBoolean(value));}
|
||||
};
|
||||
}
|
||||
private FormFieldAccessor createAccessor(final Label label, final Selection selection, final Label errorLabel) {
|
||||
private FormFieldAccessor createAccessor(final Control label, final Selection selection, final Label errorLabel) {
|
||||
switch (selection.type()) {
|
||||
case MULTI:
|
||||
case MULTI_COMBO:
|
||||
|
@ -325,7 +342,7 @@ public final class Form implements FormBinding {
|
|||
}
|
||||
}
|
||||
private FormFieldAccessor createAccessor(
|
||||
final Label label,
|
||||
final Control label,
|
||||
final Selection selection,
|
||||
final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter,
|
||||
final Label errorLabel) {
|
||||
|
@ -340,7 +357,7 @@ public final class Form implements FormBinding {
|
|||
@Override public void setStringValue(final String value) { selection.select(value); }
|
||||
};
|
||||
}
|
||||
private FormFieldAccessor createAccessor(final Label label, final ThresholdList thresholdList, final Label errorLabel) {
|
||||
private FormFieldAccessor createAccessor(final Control label, final ThresholdList thresholdList, final Label errorLabel) {
|
||||
return new FormFieldAccessor(label, thresholdList, null, true, errorLabel) {
|
||||
@Override public String getStringValue() {
|
||||
return ThresholdListBuilder
|
||||
|
@ -358,12 +375,12 @@ public final class Form implements FormBinding {
|
|||
}
|
||||
};
|
||||
}
|
||||
private FormFieldAccessor createAccessor(final Label label, final ImageUploadSelection imageUpload, final Label errorLabel) {
|
||||
private FormFieldAccessor createAccessor(final Control label, final ImageUploadSelection imageUpload, final Label errorLabel) {
|
||||
return new FormFieldAccessor(label, imageUpload, errorLabel) {
|
||||
@Override public String getStringValue() { return imageUpload.getImageBase64(); }
|
||||
};
|
||||
}
|
||||
private FormFieldAccessor createAccessor(final Label label, final FileUploadSelection fileUpload, final Label errorLabel) {
|
||||
private FormFieldAccessor createAccessor(final Control label, final FileUploadSelection fileUpload, final Label errorLabel) {
|
||||
return new FormFieldAccessor(label, fileUpload, errorLabel) {
|
||||
@Override public String getStringValue() { return fileUpload.getFileName(); }
|
||||
};
|
||||
|
@ -418,7 +435,7 @@ public final class Form implements FormBinding {
|
|||
}
|
||||
}
|
||||
|
||||
private static final void adaptCommaSeparatedStringToJsonArray(
|
||||
private static void adaptCommaSeparatedStringToJsonArray(
|
||||
final Tuple<String> tuple,
|
||||
final ObjectNode jsonNode) {
|
||||
|
||||
|
@ -433,26 +450,26 @@ public final class Form implements FormBinding {
|
|||
|
||||
public static abstract class FormFieldAccessor {
|
||||
|
||||
public final Label label;
|
||||
public final Control control;
|
||||
public final Control label;
|
||||
public final Control input;
|
||||
private final Label errorLabel;
|
||||
private final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter;
|
||||
private boolean hasError;
|
||||
private final boolean listValue;
|
||||
|
||||
FormFieldAccessor(final Label label, final Control control, final Label errorLabel) {
|
||||
FormFieldAccessor(final Control label, final Control control, final Label errorLabel) {
|
||||
this(label, control, null, false, errorLabel);
|
||||
}
|
||||
|
||||
FormFieldAccessor(
|
||||
final Label label,
|
||||
final Control control,
|
||||
final Control label,
|
||||
final Control input,
|
||||
final BiConsumer<Tuple<String>, ObjectNode> jsonValueAdapter,
|
||||
final boolean listValue,
|
||||
final Label errorLabel) {
|
||||
|
||||
this.label = label;
|
||||
this.control = control;
|
||||
this.input = input;
|
||||
this.errorLabel = errorLabel;
|
||||
if (jsonValueAdapter != null) {
|
||||
this.jsonValueAdapter = jsonValueAdapter;
|
||||
|
@ -473,14 +490,14 @@ public final class Form implements FormBinding {
|
|||
}
|
||||
|
||||
public void setBackgroundColor(final Color color) {
|
||||
if (this.control != null) {
|
||||
this.control.setBackground(color);
|
||||
if (this.input != null) {
|
||||
this.input.setBackground(color);
|
||||
}
|
||||
}
|
||||
|
||||
public void setTextColor(final Color color) {
|
||||
if (this.control != null) {
|
||||
this.control.setForeground(color);
|
||||
if (this.input != null) {
|
||||
this.input.setForeground(color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -488,7 +505,7 @@ public final class Form implements FormBinding {
|
|||
if (this.label != null) {
|
||||
this.label.setVisible(visible);
|
||||
}
|
||||
this.control.setVisible(visible);
|
||||
this.input.setVisible(visible);
|
||||
}
|
||||
|
||||
public void putJsonValue(final String key, final ObjectNode objectRoot) {
|
||||
|
@ -506,7 +523,7 @@ public final class Form implements FormBinding {
|
|||
}
|
||||
|
||||
if (!this.hasError) {
|
||||
this.control.setData(RWT.CUSTOM_VARIANT, CustomVariant.ERROR.key);
|
||||
this.input.setData(RWT.CUSTOM_VARIANT, CustomVariant.ERROR.key);
|
||||
this.errorLabel.setText("- " + errorMessage);
|
||||
this.errorLabel.setVisible(true);
|
||||
this.hasError = true;
|
||||
|
@ -519,7 +536,7 @@ public final class Form implements FormBinding {
|
|||
}
|
||||
|
||||
if (this.hasError) {
|
||||
this.control.setData(RWT.CUSTOM_VARIANT, null);
|
||||
this.input.setData(RWT.CUSTOM_VARIANT, null);
|
||||
this.errorLabel.setVisible(false);
|
||||
this.errorLabel.setText(StringUtils.EMPTY);
|
||||
this.hasError = false;
|
||||
|
|
|
@ -12,8 +12,10 @@ import java.util.Arrays;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
|
@ -40,6 +42,7 @@ public class FormBuilder {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(FormBuilder.class);
|
||||
|
||||
final Cryptor cryptor;
|
||||
final I18nSupport i18nSupport;
|
||||
final PageService pageService;
|
||||
final WidgetFactory widgetFactory;
|
||||
|
@ -56,13 +59,15 @@ public class FormBuilder {
|
|||
public FormBuilder(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final Cryptor cryptor,
|
||||
final int rows) {
|
||||
|
||||
this.cryptor = cryptor;
|
||||
this.i18nSupport = pageService.getI18nSupport();
|
||||
this.pageService = pageService;
|
||||
this.widgetFactory = pageService.getWidgetFactory();
|
||||
this.pageContext = pageContext;
|
||||
this.form = new Form(pageService.getJSONMapper());
|
||||
this.form = new Form(pageService.getJSONMapper(), cryptor);
|
||||
|
||||
this.formParent = this.widgetFactory.formGrid(
|
||||
pageContext.getParent(),
|
||||
|
@ -214,11 +219,18 @@ public class FormBuilder {
|
|||
return new TextFieldBuilder(name, label, value);
|
||||
}
|
||||
|
||||
public static TextFieldBuilder text(final String name, final LocTextKey label,
|
||||
public static TextFieldBuilder text(
|
||||
final String name,
|
||||
final LocTextKey label,
|
||||
final Supplier<String> valueSupplier) {
|
||||
|
||||
return new TextFieldBuilder(name, label, valueSupplier.get());
|
||||
}
|
||||
|
||||
public static PasswordFieldBuilder password(final String name, final LocTextKey label, final CharSequence value) {
|
||||
return new PasswordFieldBuilder(name, label, value);
|
||||
}
|
||||
|
||||
public static SelectionFieldBuilder singleSelection(
|
||||
final String name,
|
||||
final LocTextKey label,
|
||||
|
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.form;
|
|||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
|
@ -37,7 +38,7 @@ public final class ImageUploadFieldBuilder extends FieldBuilder<String> {
|
|||
|
||||
@Override
|
||||
void build(final FormBuilder builder) {
|
||||
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
|
||||
final ImageUploadSelection imageUpload = builder.widgetFactory.imageUploadLocalized(
|
||||
fieldGrid,
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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.form;
|
||||
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.widget.PasswordInput;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
|
||||
public class PasswordFieldBuilder extends FieldBuilder<CharSequence> {
|
||||
|
||||
PasswordFieldBuilder(final String name, final LocTextKey label, final CharSequence value) {
|
||||
super(name, label, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void build(FormBuilder builder) {
|
||||
final boolean readonly = builder.readonly || this.readonly;
|
||||
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
|
||||
|
||||
final PasswordInput input = new PasswordInput(fieldGrid, builder.widgetFactory);
|
||||
input.setEditable(!readonly);
|
||||
input.setValue((StringUtils.isNotBlank(this.value))
|
||||
? builder.cryptor.decrypt(this.value)
|
||||
: this.value);
|
||||
|
||||
final Label errorLabel = createErrorLabel(fieldGrid);
|
||||
builder.form.putField(this.name, titleLabel, input, errorLabel);
|
||||
}
|
||||
}
|
|
@ -55,7 +55,7 @@ public final class SelectionFieldBuilder extends FieldBuilder<String> {
|
|||
|
||||
@Override
|
||||
void build(final FormBuilder builder) {
|
||||
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
|
||||
if (builder.readonly || this.readonly) {
|
||||
buildReadOnly(builder, titleLabel);
|
||||
|
@ -64,7 +64,7 @@ public final class SelectionFieldBuilder extends FieldBuilder<String> {
|
|||
}
|
||||
}
|
||||
|
||||
private void buildInput(final FormBuilder builder, final Label titleLabel) {
|
||||
private void buildInput(final FormBuilder builder, final Control titleLabel) {
|
||||
|
||||
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
|
||||
final String actionKey = (this.label != null) ? this.label.name + ".action" : null;
|
||||
|
@ -93,7 +93,7 @@ public final class SelectionFieldBuilder extends FieldBuilder<String> {
|
|||
}
|
||||
|
||||
/* Build the read-only representation of the selection field */
|
||||
private void buildReadOnly(final FormBuilder builder, final Label titleLabel) {
|
||||
private void buildReadOnly(final FormBuilder builder, final Control titleLabel) {
|
||||
if (this.type == Type.MULTI || this.type == Type.MULTI_COMBO || this.type == Type.MULTI_CHECKBOX) {
|
||||
final Composite composite = new Composite(builder.formParent, SWT.NONE);
|
||||
final GridLayout gridLayout = new GridLayout(1, true);
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.eclipse.swt.graphics.Color;
|
|||
import org.eclipse.swt.graphics.RGB;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
|
@ -88,7 +89,7 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
|
|||
@Override
|
||||
void build(final FormBuilder builder) {
|
||||
final boolean readonly = builder.readonly || this.readonly;
|
||||
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
|
||||
|
||||
if (readonly && this.isHTML) {
|
||||
|
@ -117,7 +118,7 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
|
|||
gridData.minimumHeight = this.areaMinHeight;
|
||||
} else if (this.isColorBox) {
|
||||
gridData.minimumHeight = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
|
||||
textInput.setData(RWT.CUSTOM_VARIANT, "colorbox");
|
||||
textInput.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.COLOR_BOX.key);
|
||||
}
|
||||
textInput.setLayoutData(gridData);
|
||||
if (StringUtils.isNoneBlank(this.value)) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
|
@ -39,7 +40,7 @@ public class ThresholdListBuilder extends FieldBuilder<Collection<Threshold>> {
|
|||
|
||||
@Override
|
||||
void build(final FormBuilder builder) {
|
||||
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
|
||||
if (builder.readonly || this.readonly) {
|
||||
// No read-only view needed for this so far?
|
||||
return;
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.util.function.Function;
|
|||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Tuple3;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -107,6 +108,7 @@ public class ResourceService {
|
|||
public static final String CONFIG_ATTRIBUTE_TYPE_PREFIX = "sebserver.configtemplate.attr.type.";
|
||||
public static final String SEB_RESTRICTION_WHITE_LIST_PREFIX = "sebserver.exam.form.sebrestriction.whiteListPaths.";
|
||||
public static final String SEB_RESTRICTION_PERMISSIONS_PREFIX = "sebserver.exam.form.sebrestriction.permissions.";
|
||||
public static final String SEB_CLIENT_CONFIG_PURPOSE_PREFIX = "sebserver.clientconfig.config.purpose.";
|
||||
|
||||
public static final EnumSet<AttributeType> ATTRIBUTE_TYPES_NOT_DISPLAYED = EnumSet.of(
|
||||
AttributeType.LABEL,
|
||||
|
@ -656,4 +658,16 @@ public class ResourceService {
|
|||
.call();
|
||||
}
|
||||
|
||||
public List<Tuple<String>> sebClientConfigPurposeResources() {
|
||||
return Arrays.stream(SebClientConfig.ConfigPurpose.values())
|
||||
.map(type -> new Tuple3<>(
|
||||
type.name(),
|
||||
this.i18nSupport.getText(SEB_CLIENT_CONFIG_PURPOSE_PREFIX + type.name()),
|
||||
Utils.formatLineBreaks(this.i18nSupport.getText(
|
||||
SEB_CLIENT_CONFIG_PURPOSE_PREFIX + type.name() + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
|
||||
StringUtils.EMPTY))))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,44 +8,51 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.service.examconfig.impl;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
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.Cryptor;
|
||||
import ch.ethz.seb.sebserver.gui.form.FieldBuilder;
|
||||
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.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.widget.PasswordInput;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Listener;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
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.gui.form.FieldBuilder;
|
||||
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.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class PassworFieldBuilder implements InputFieldBuilder {
|
||||
public class PasswordFieldBuilder implements InputFieldBuilder {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PassworFieldBuilder.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(PasswordFieldBuilder.class);
|
||||
|
||||
private static final LocTextKey VAL_CONFIRM_PWD_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.props.validation.password.confirm");
|
||||
|
||||
private final Cryptor cryptor;
|
||||
private final WidgetFactory widgetFactory;
|
||||
|
||||
public PasswordFieldBuilder(
|
||||
final WidgetFactory widgetFactory,
|
||||
final Cryptor cryptor) {
|
||||
|
||||
this.cryptor = cryptor;
|
||||
this.widgetFactory = widgetFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean builderFor(
|
||||
final ConfigurationAttribute attribute,
|
||||
|
@ -69,24 +76,26 @@ public class PassworFieldBuilder implements InputFieldBuilder {
|
|||
final Composite innerGrid = InputFieldBuilder
|
||||
.createInnerGrid(parent, attribute, orientation);
|
||||
|
||||
final Text passwordInput = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.PASSWORD);
|
||||
final GridData passwordInputLD = new GridData(SWT.FILL, SWT.FILL, true, false);
|
||||
final PasswordInput passwordInput = new PasswordInput(innerGrid, widgetFactory);
|
||||
final GridData passwordInputLD = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
passwordInput.setLayoutData(passwordInputLD);
|
||||
final Text confirmInput = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.PASSWORD);
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
|
||||
|
||||
final PasswordInput confirmInput = new PasswordInput(innerGrid, widgetFactory);
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
gridData.verticalIndent = 14;
|
||||
confirmInput.setLayoutData(gridData);
|
||||
innerGrid.setData("isPlainText", false);
|
||||
|
||||
final PasswordInputField passwordInputField = new PasswordInputField(
|
||||
attribute,
|
||||
orientation,
|
||||
passwordInput,
|
||||
confirmInput,
|
||||
FieldBuilder.createErrorLabel(innerGrid));
|
||||
FieldBuilder.createErrorLabel(innerGrid),
|
||||
cryptor);
|
||||
|
||||
if (viewContext.readonly) {
|
||||
passwordInput.setEditable(false);
|
||||
passwordInput.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key);
|
||||
passwordInputLD.heightHint = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
|
||||
confirmInput.setEditable(false);
|
||||
confirmInput.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key);
|
||||
|
@ -95,8 +104,8 @@ public class PassworFieldBuilder implements InputFieldBuilder {
|
|||
final Listener valueChangeEventListener = event -> {
|
||||
passwordInputField.clearError();
|
||||
|
||||
final String pwd = passwordInput.getText();
|
||||
final String confirm = confirmInput.getText();
|
||||
final CharSequence pwd = passwordInput.getValue();
|
||||
final CharSequence confirm = confirmInput.getValue();
|
||||
|
||||
if (passwordInputField.initValue != null && passwordInputField.initValue.equals(pwd)) {
|
||||
return;
|
||||
|
@ -127,50 +136,44 @@ public class PassworFieldBuilder implements InputFieldBuilder {
|
|||
return passwordInputField;
|
||||
}
|
||||
|
||||
static final class PasswordInputField extends AbstractInputField<Text> {
|
||||
static final class PasswordInputField extends AbstractInputField<PasswordInput> {
|
||||
|
||||
private final Text confirm;
|
||||
private final PasswordInput confirm;
|
||||
private final Cryptor cryptor;
|
||||
|
||||
PasswordInputField(
|
||||
final ConfigurationAttribute attribute,
|
||||
final Orientation orientation,
|
||||
final Text control,
|
||||
final Text confirm,
|
||||
final Label errorLabel) {
|
||||
final PasswordInput control,
|
||||
final PasswordInput confirm,
|
||||
final Label errorLabel,
|
||||
final Cryptor cryptor) {
|
||||
|
||||
super(attribute, orientation, control, errorLabel);
|
||||
this.confirm = confirm;
|
||||
this.cryptor = cryptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setValueToControl(final String value) {
|
||||
// TODO clarify setting some "fake" input when a password is set (like in config tool)
|
||||
if (value != null) {
|
||||
this.control.setText(value);
|
||||
this.confirm.setText(value);
|
||||
if (StringUtils.isNotBlank(value)) {
|
||||
CharSequence pwd = cryptor.decrypt(value);
|
||||
this.control.setValue(pwd.toString());
|
||||
this.confirm.setValue(pwd.toString());
|
||||
} else {
|
||||
this.control.setValue(StringUtils.EMPTY);
|
||||
this.confirm.setValue(StringUtils.EMPTY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
String hashedPWD;
|
||||
try {
|
||||
hashedPWD = hashPassword(this.control.getText());
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
log.error("Failed to hash password: ", e);
|
||||
showError("Failed to hash password");
|
||||
hashedPWD = null;
|
||||
final CharSequence pwd = this.control.getValue();
|
||||
if (StringUtils.isNotBlank(pwd)) {
|
||||
return cryptor.encrypt(pwd).toString();
|
||||
}
|
||||
|
||||
return hashedPWD;
|
||||
}
|
||||
|
||||
private String hashPassword(final String pwd) throws NoSuchAlgorithmException {
|
||||
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
final byte[] encodedhash = digest.digest(
|
||||
pwd.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
return Hex.encodeHexString(encodedhash);
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@ import java.util.function.Supplier;
|
|||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -78,6 +79,7 @@ public class PageServiceImpl implements PageService {
|
|||
private static final String ATTR_PAGE_STATE = "PAGE_STATE";
|
||||
private static final ListenerComparator LIST_COMPARATOR = new ListenerComparator();
|
||||
|
||||
private final Cryptor cryptor;
|
||||
private final JSONMapper jsonMapper;
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final PolyglotPageService polyglotPageService;
|
||||
|
@ -85,12 +87,14 @@ public class PageServiceImpl implements PageService {
|
|||
private final CurrentUser currentUser;
|
||||
|
||||
public PageServiceImpl(
|
||||
final Cryptor cryptor,
|
||||
final JSONMapper jsonMapper,
|
||||
final WidgetFactory widgetFactory,
|
||||
final PolyglotPageService polyglotPageService,
|
||||
final ResourceService resourceService,
|
||||
final CurrentUser currentUser) {
|
||||
|
||||
this.cryptor = cryptor;
|
||||
this.jsonMapper = jsonMapper;
|
||||
this.widgetFactory = widgetFactory;
|
||||
this.polyglotPageService = polyglotPageService;
|
||||
|
@ -337,7 +341,7 @@ public class PageServiceImpl implements PageService {
|
|||
|
||||
@Override
|
||||
public FormBuilder formBuilder(final PageContext pageContext, final int rows) {
|
||||
return new FormBuilder(this, pageContext, rows);
|
||||
return new FormBuilder(this, pageContext, cryptor, rows);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* 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.widget;
|
||||
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
public class PasswordInput extends Composite {
|
||||
|
||||
public static final LocTextKey PLAIN_TEXT_VIEW_TOOLTIP_KEY =
|
||||
new LocTextKey("sebserver.overall.action.showPassword.tooltip");
|
||||
|
||||
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final Composite inputAnchor;
|
||||
private final Label visibilityButton;
|
||||
|
||||
private Text passwordInput = null;
|
||||
private boolean isPlainText = true;
|
||||
private boolean isEditable = true;
|
||||
|
||||
public PasswordInput(final Composite parent, final WidgetFactory widgetFactory) {
|
||||
super(parent, SWT.NONE);
|
||||
this.widgetFactory = widgetFactory;
|
||||
|
||||
GridLayout gridLayout = new GridLayout(2, false);
|
||||
gridLayout.horizontalSpacing = 0;
|
||||
gridLayout.verticalSpacing = 0;
|
||||
gridLayout.marginHeight = 0;
|
||||
gridLayout.marginWidth = 0;
|
||||
this.setLayout(gridLayout);
|
||||
this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
|
||||
|
||||
inputAnchor = new Composite(this, SWT.NONE);
|
||||
gridLayout = new GridLayout(1, false);
|
||||
gridLayout.horizontalSpacing = 0;
|
||||
gridLayout.verticalSpacing = 0;
|
||||
gridLayout.marginHeight = 0;
|
||||
gridLayout.marginWidth = 0;
|
||||
inputAnchor.setLayout(gridLayout);
|
||||
inputAnchor.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
|
||||
|
||||
|
||||
|
||||
visibilityButton = widgetFactory.imageButton(
|
||||
WidgetFactory.ImageIcon.VISIBILITY,
|
||||
this,
|
||||
PLAIN_TEXT_VIEW_TOOLTIP_KEY,
|
||||
event -> changePasswordView());
|
||||
GridData ld = new GridData(SWT.RIGHT, SWT.BOTTOM, false, false);
|
||||
ld.heightHint = 22;
|
||||
ld.horizontalIndent = 5;
|
||||
visibilityButton.setLayoutData(ld);
|
||||
|
||||
changePasswordView();
|
||||
|
||||
}
|
||||
|
||||
private void changePasswordView() {
|
||||
final String value = (this.passwordInput != null) ? this.passwordInput.getText() : null;
|
||||
final boolean buildPassword = this.isPlainText;
|
||||
|
||||
if (this.passwordInput != null) {
|
||||
PageService.clearComposite(this.inputAnchor);
|
||||
}
|
||||
|
||||
Text passwordInput = new Text(
|
||||
inputAnchor,
|
||||
SWT.LEFT | SWT.BORDER | (buildPassword ? SWT.PASSWORD : SWT.NONE));
|
||||
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
passwordInput.setLayoutData(gridData);
|
||||
passwordInput.setText(value != null ? value : StringUtils.EMPTY);
|
||||
if (!buildPassword) {
|
||||
passwordInput.setEditable(false);
|
||||
} else {
|
||||
passwordInput.setEditable(isEditable);
|
||||
passwordInput.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.CONFIG_INPUT_READONLY.key);
|
||||
if (!isEditable) {
|
||||
gridData.heightHint = 21;
|
||||
}
|
||||
}
|
||||
|
||||
if (buildPassword) {
|
||||
passwordInput.addListener(SWT.FocusOut, event -> super.notifyListeners(SWT.FocusOut, event));
|
||||
passwordInput.addListener(SWT.Traverse, event -> super.notifyListeners(SWT.Traverse, event));
|
||||
this.visibilityButton.setImage(WidgetFactory.ImageIcon.VISIBILITY.getImage(getDisplay()));
|
||||
} else {
|
||||
passwordInput.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.PLAIN_PWD.key);
|
||||
this.visibilityButton.setImage(WidgetFactory.ImageIcon.VISIBILITY_OFF.getImage(getDisplay()));
|
||||
}
|
||||
|
||||
this.passwordInput = passwordInput;
|
||||
this.isPlainText = !this.isPlainText;
|
||||
|
||||
super.layout(true, true);
|
||||
}
|
||||
|
||||
public void setValue(CharSequence value) {
|
||||
if (passwordInput != null) {
|
||||
passwordInput.setText(value != null ? value.toString() : StringUtils.EMPTY);
|
||||
}
|
||||
}
|
||||
|
||||
public CharSequence getValue() {
|
||||
if (passwordInput != null) {
|
||||
return passwordInput.getText();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public void setEditable(boolean editable) {
|
||||
this.isEditable = editable;
|
||||
this.isPlainText = !this.isPlainText;
|
||||
this.changePasswordView();
|
||||
}
|
||||
}
|
|
@ -115,7 +115,9 @@ public class WidgetFactory {
|
|||
HELP("help.png"),
|
||||
LOCK("lock.png"),
|
||||
UNLOCK("unlock.png"),
|
||||
RESTRICTION("restriction.png");
|
||||
RESTRICTION("restriction.png"),
|
||||
VISIBILITY("visibility.png"),
|
||||
VISIBILITY_OFF("visibility_off.png");
|
||||
|
||||
public String fileName;
|
||||
private ImageData image = null;
|
||||
|
@ -189,7 +191,9 @@ public class WidgetFactory {
|
|||
LOGIN_BACK("login-back"),
|
||||
SCROLL("scroll"),
|
||||
|
||||
LIST_NAVIGATION("list-nav")
|
||||
LIST_NAVIGATION("list-nav"),
|
||||
PLAIN_PWD("pwdplain"),
|
||||
COLOR_BOX("colorbox")
|
||||
|
||||
;
|
||||
|
||||
|
@ -403,7 +407,7 @@ public class WidgetFactory {
|
|||
|
||||
public Text numberInput(final Composite content, final Consumer<String> numberCheck, final boolean readonly) {
|
||||
if (readonly) {
|
||||
return new Text(content, SWT.RIGHT | SWT.READ_ONLY);
|
||||
return new Text(content, SWT.LEFT | SWT.READ_ONLY);
|
||||
}
|
||||
|
||||
final Text numberInput = new Text(content, SWT.RIGHT | SWT.BORDER);
|
||||
|
|
|
@ -8,22 +8,20 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.client;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.CharBuffer;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.security.crypto.encrypt.Encryptors;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.CharBuffer;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
|
@ -32,12 +30,15 @@ public class ClientCredentialServiceImpl implements ClientCredentialService {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(ClientCredentialServiceImpl.class);
|
||||
|
||||
static final String SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY = "sebserver.webservice.internalSecret";
|
||||
|
||||
private final Environment environment;
|
||||
private final Cryptor cryptor;
|
||||
|
||||
protected ClientCredentialServiceImpl(
|
||||
final Environment environment,
|
||||
final Cryptor cryptor) {
|
||||
|
||||
protected ClientCredentialServiceImpl(final Environment environment) {
|
||||
this.environment = environment;
|
||||
this.cryptor = cryptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -63,15 +64,15 @@ public class ClientCredentialServiceImpl implements ClientCredentialService {
|
|||
final CharSequence accessTokenPlaintext) {
|
||||
|
||||
final CharSequence secret = this.environment
|
||||
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
|
||||
.getProperty(Cryptor.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
|
||||
|
||||
return new ClientCredentials(
|
||||
clientIdPlaintext,
|
||||
(StringUtils.isNoneBlank(secretPlaintext))
|
||||
? encrypt(secretPlaintext, secret).toString()
|
||||
? Cryptor.encrypt(secretPlaintext, secret).toString()
|
||||
: null,
|
||||
(StringUtils.isNoneBlank(accessTokenPlaintext))
|
||||
? encrypt(accessTokenPlaintext, secret).toString()
|
||||
? Cryptor.encrypt(accessTokenPlaintext, secret).toString()
|
||||
: null);
|
||||
}
|
||||
|
||||
|
@ -82,8 +83,8 @@ public class ClientCredentialServiceImpl implements ClientCredentialService {
|
|||
}
|
||||
|
||||
final CharSequence secret = this.environment
|
||||
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
|
||||
return this.decrypt(credentials.secret, secret);
|
||||
.getProperty(Cryptor.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
|
||||
return Cryptor.decrypt(credentials.secret, secret);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -93,100 +94,39 @@ public class ClientCredentialServiceImpl implements ClientCredentialService {
|
|||
}
|
||||
|
||||
final CharSequence secret = this.environment
|
||||
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
|
||||
.getProperty(Cryptor.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
|
||||
|
||||
return this.decrypt(credentials.accessToken, secret);
|
||||
return Cryptor.decrypt(credentials.accessToken, secret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence encrypt(final CharSequence text) {
|
||||
|
||||
final CharSequence secret = this.environment
|
||||
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
|
||||
|
||||
return encrypt(text, secret);
|
||||
return cryptor.encrypt(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence decrypt(final CharSequence text) {
|
||||
|
||||
final CharSequence secret = this.environment
|
||||
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
|
||||
|
||||
return decrypt(text, secret);
|
||||
}
|
||||
|
||||
CharSequence encrypt(final CharSequence text, final CharSequence secret) {
|
||||
if (text == null) {
|
||||
throw new IllegalArgumentException("Text has null reference");
|
||||
}
|
||||
|
||||
if (secret == null) {
|
||||
log.warn("No internal secret supplied: skip encryption");
|
||||
return text;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
final CharSequence salt = KeyGenerators.string().generateKey();
|
||||
final CharSequence cipher = Encryptors
|
||||
.delux(secret, salt)
|
||||
.encrypt(text.toString());
|
||||
|
||||
return new StringBuilder(cipher)
|
||||
.append(salt);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to encrypt text: ", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
CharSequence decrypt(final CharSequence cipher, final CharSequence secret) {
|
||||
if (cipher == null) {
|
||||
throw new IllegalArgumentException("Cipher has null reference");
|
||||
}
|
||||
|
||||
if (secret == null) {
|
||||
log.warn("No internal secret supplied: skip decryption");
|
||||
return cipher;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
final int length = cipher.length();
|
||||
final int cipherTextLength = length - 16;
|
||||
final CharSequence salt = cipher.subSequence(cipherTextLength, length);
|
||||
final CharSequence cipherText = cipher.subSequence(0, cipherTextLength);
|
||||
|
||||
return Encryptors
|
||||
.delux(secret, salt)
|
||||
.decrypt(cipherText.toString());
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to decrypt text: ", e);
|
||||
throw e;
|
||||
}
|
||||
return cryptor.decrypt(text);
|
||||
}
|
||||
|
||||
private final static char[] possibleCharacters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^*()-_=+[{]}?"
|
||||
.toCharArray();
|
||||
|
||||
public final static CharSequence generateClientId() {
|
||||
public static CharSequence generateClientId() {
|
||||
return RandomStringUtils.random(
|
||||
16, 0, possibleCharacters.length - 1, false, false,
|
||||
possibleCharacters, new SecureRandom());
|
||||
}
|
||||
|
||||
public final static CharSequence generateClientSecret() throws UnsupportedEncodingException {
|
||||
// TODO fine a better way to generate a random char array instead of using RandomStringUtils.random which uses a String
|
||||
public static CharSequence generateClientSecret() throws UnsupportedEncodingException {
|
||||
// TODO find a better way to generate a random char array instead of using RandomStringUtils.random which uses a String
|
||||
return RandomStringUtils.random(
|
||||
64, 0, possibleCharacters.length - 1, false, false,
|
||||
possibleCharacters, new SecureRandom());
|
||||
}
|
||||
|
||||
public final static void clearChars(final CharSequence sequence) {
|
||||
public static void clearChars(final CharSequence sequence) {
|
||||
if (sequence == null) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
/ExamDAO.java
|
|
@ -30,7 +30,7 @@ public interface ExamConfigurationMapDAO extends
|
|||
* @param examId The Exam mapping identifier
|
||||
* @param configurationNodeId the ConfigurationNode mapping identifier
|
||||
* @return Result refer to the password cipher of specified mapping or to an exception if happened */
|
||||
Result<CharSequence> getConfigPasswortCipher(Long examId, Long configurationNodeId);
|
||||
Result<CharSequence> getConfigPasswordCipher(Long examId, Long configurationNodeId);
|
||||
|
||||
/** Get the ConfigurationNode identifier of the default Exam Configuration of
|
||||
* the Exam with specified identifier.
|
||||
|
|
|
@ -43,7 +43,7 @@ public interface SebClientConfigDAO extends
|
|||
*
|
||||
* @param modelId the model
|
||||
* @return encrypted configuration password */
|
||||
Result<CharSequence> getConfigPasswortCipher(String modelId);
|
||||
Result<CharSequence> getConfigPasswordCipher(String modelId);
|
||||
|
||||
/** Get the stored encrypted configuration password from a specified SEB client configuration.
|
||||
* The SEB client configuration password is used to encrypt a SEB Client Configuration.
|
||||
|
@ -52,7 +52,7 @@ public interface SebClientConfigDAO extends
|
|||
*
|
||||
* @param clientName the client name
|
||||
* @return encrypted configuration password */
|
||||
Result<CharSequence> getConfigPasswortCipherByClientName(String clientName);
|
||||
Result<CharSequence> getConfigPasswordCipherByClientName(String clientName);
|
||||
|
||||
@Override
|
||||
@CacheEvict(
|
||||
|
|
|
@ -99,16 +99,14 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
|||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<ClientConnection>> allOf(final Set<Long> pks) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.clientConnectionRecordMapper.selectByExample()
|
||||
return Result.tryCatch(() -> this.clientConnectionRecordMapper.selectByExample()
|
||||
.where(ClientConnectionRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(ClientConnectionDAOImpl::toDomainModel)
|
||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -77,9 +77,7 @@ public class ClientEventDAOImpl implements ClientEventDAO {
|
|||
final FilterMap filterMap,
|
||||
final Predicate<ClientEvent> predicate) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
return this.clientEventRecordMapper
|
||||
return Result.tryCatch(() -> this.clientEventRecordMapper
|
||||
.selectByExample()
|
||||
.where(
|
||||
ClientEventRecordDynamicSqlSupport.clientConnectionId,
|
||||
|
@ -111,8 +109,7 @@ public class ClientEventDAOImpl implements ClientEventDAO {
|
|||
.map(ClientEventDAOImpl::toDomainModel)
|
||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||
.filter(predicate)
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -166,16 +163,14 @@ public class ClientEventDAOImpl implements ClientEventDAO {
|
|||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<ClientEvent>> allOf(final Set<Long> pks) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.clientEventRecordMapper.selectByExample()
|
||||
return Result.tryCatch(() -> this.clientEventRecordMapper.selectByExample()
|
||||
.where(ClientEventRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(ClientEventDAOImpl::toDomainModel)
|
||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -246,9 +241,7 @@ public class ClientEventDAOImpl implements ClientEventDAO {
|
|||
}
|
||||
|
||||
private static Result<ExtendedClientEvent> toDomainModelExtended(final ConnectionEventJoinRecord record) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
return new ExtendedClientEvent(
|
||||
return Result.tryCatch(() -> new ExtendedClientEvent(
|
||||
record.institution_id,
|
||||
record.exam_id,
|
||||
record.exam_user_session_identifer,
|
||||
|
@ -257,9 +250,8 @@ public class ClientEventDAOImpl implements ClientEventDAO {
|
|||
(record.type != null) ? EventType.byId(record.type) : EventType.UNKNOWN,
|
||||
record.client_time,
|
||||
record.server_time,
|
||||
(record.numeric_value != null) ? record.numeric_value.doubleValue() : null,
|
||||
record.text);
|
||||
});
|
||||
record.numeric_value,
|
||||
record.text));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -75,16 +75,14 @@ public class ConfigurationAttributeDAOImpl implements ConfigurationAttributeDAO
|
|||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<ConfigurationAttribute>> allOf(final Set<Long> pks) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.configurationAttributeRecordMapper.selectByExample()
|
||||
return Result.tryCatch(() -> this.configurationAttributeRecordMapper.selectByExample()
|
||||
.where(ConfigurationAttributeRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(ConfigurationAttributeDAOImpl::toDomainModel)
|
||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -133,7 +133,7 @@ class ConfigurationDAOBatchService {
|
|||
.build()
|
||||
.execute();
|
||||
|
||||
if (count != null && count.longValue() > 0) {
|
||||
if (count != null && count > 0) {
|
||||
throw new FieldValidationException("name", "configurationNode:name:exists");
|
||||
}
|
||||
|
||||
|
@ -231,7 +231,7 @@ class ConfigurationDAOBatchService {
|
|||
oldValRec.getConfigurationAttributeId(),
|
||||
oldValRec.getListIndex(),
|
||||
oldValRec.getValue()))
|
||||
.forEach(newValRec -> this.batchConfigurationValueRecordMapper.insert(newValRec));
|
||||
.forEach(this.batchConfigurationValueRecordMapper::insert);
|
||||
|
||||
return this.batchConfigurationRecordMapper
|
||||
.selectByPrimaryKey(newFollowup.getId());
|
||||
|
@ -337,7 +337,7 @@ class ConfigurationDAOBatchService {
|
|||
historicValRec.getConfigurationAttributeId(),
|
||||
historicValRec.getListIndex(),
|
||||
historicValRec.getValue()))
|
||||
.forEach(newValRec -> this.batchConfigurationValueRecordMapper.insert(newValRec));
|
||||
.forEach(this.batchConfigurationValueRecordMapper::insert);
|
||||
|
||||
return followup;
|
||||
})
|
||||
|
@ -361,7 +361,7 @@ class ConfigurationDAOBatchService {
|
|||
.build()
|
||||
.execute();
|
||||
|
||||
if (count != null && count.longValue() > 0) {
|
||||
if (count != null && count > 0) {
|
||||
throw new FieldValidationException("name", "configurationNode:name:exists");
|
||||
}
|
||||
|
||||
|
@ -405,9 +405,7 @@ class ConfigurationDAOBatchService {
|
|||
.execute();
|
||||
|
||||
if (BooleanUtils.toBoolean(copyInfo.withHistory)) {
|
||||
configs
|
||||
.stream()
|
||||
.forEach(configRec -> this.copyConfiguration(
|
||||
configs.forEach(configRec -> this.copyConfiguration(
|
||||
configRec.getInstitutionId(),
|
||||
configRec.getId(),
|
||||
newNodeRec.getId()));
|
||||
|
@ -540,11 +538,11 @@ class ConfigurationDAOBatchService {
|
|||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(rec -> rec.getId(), Function.identity()));
|
||||
.collect(Collectors.toMap(ConfigurationAttributeRecord::getId, Function.identity()));
|
||||
|
||||
final List<Long> columnAttributeIds = attributeMap.values()
|
||||
.stream()
|
||||
.map(a -> a.getId())
|
||||
.map(ConfigurationAttributeRecord::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// first delete all old values of this table
|
||||
|
@ -705,15 +703,13 @@ class ConfigurationDAOBatchService {
|
|||
.stream()
|
||||
// filter child attributes of tables. No default value for tables. Use templates for that
|
||||
.filter(ConfigurationDAOBatchService::filterChildAttribute)
|
||||
.forEach(attrRec -> {
|
||||
this.batchConfigurationValueRecordMapper.insert(new ConfigurationValueRecord(
|
||||
.forEach(attrRec -> this.batchConfigurationValueRecordMapper.insert(new ConfigurationValueRecord(
|
||||
null,
|
||||
configNode.institutionId,
|
||||
config.getId(),
|
||||
attrRec.getId(),
|
||||
0,
|
||||
attrRec.getDefaultValue()));
|
||||
});
|
||||
attrRec.getDefaultValue())));
|
||||
|
||||
// override with template values if available
|
||||
if (configNode.templateId == null || configNode.templateId.equals(ConfigurationNode.DEFAULT_TEMPLATE_ID)) {
|
||||
|
@ -747,7 +743,6 @@ class ConfigurationDAOBatchService {
|
|||
configNode.institutionId,
|
||||
config.getId(),
|
||||
attributeMap::get)
|
||||
.stream()
|
||||
.forEach(value -> {
|
||||
final ConfigurationValueRecord valueRec = new ConfigurationValueRecord(
|
||||
null,
|
||||
|
@ -768,8 +763,7 @@ class ConfigurationDAOBatchService {
|
|||
final ConfigurationRecord config) {
|
||||
|
||||
final List<ConfigurationValueRecord> templateValues = getTemplateValues(configNode);
|
||||
templateValues.stream()
|
||||
.forEach(templateValue -> {
|
||||
templateValues.forEach(templateValue -> {
|
||||
final Long existingId = this.batchConfigurationValueRecordMapper
|
||||
.selectIdsByExample()
|
||||
.where(
|
||||
|
|
|
@ -96,16 +96,14 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<ExamConfigurationMap>> allOf(final Set<Long> pks) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.examConfigurationMapRecordMapper.selectByExample()
|
||||
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectByExample()
|
||||
.where(ExamConfigurationMapRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(this::toDomainModel)
|
||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -155,7 +153,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<CharSequence> getConfigPasswortCipher(final Long examId, final Long configurationNodeId) {
|
||||
public Result<CharSequence> getConfigPasswordCipher(final Long examId, final Long configurationNodeId) {
|
||||
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
|
||||
.selectByExample()
|
||||
.where(
|
||||
|
@ -185,7 +183,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(mapping -> mapping.getConfigurationNodeId())
|
||||
.map(ExamConfigurationMapRecord::getConfigurationNodeId)
|
||||
.collect(Utils.toSingleton()));
|
||||
}
|
||||
|
||||
|
@ -203,7 +201,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(mapping -> mapping.getConfigurationNodeId())
|
||||
.map(ExamConfigurationMapRecord::getConfigurationNodeId)
|
||||
.collect(Utils.toSingleton()));
|
||||
}
|
||||
|
||||
|
@ -218,7 +216,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(mapping -> mapping.getConfigurationNodeId())
|
||||
.map(ExamConfigurationMapRecord::getConfigurationNodeId)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
|
@ -313,24 +311,21 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<Long>> getExamIdsForConfigNodeId(final Long configurationNodeId) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.examConfigurationMapRecordMapper.selectByExample()
|
||||
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectByExample()
|
||||
.where(
|
||||
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
|
||||
isEqualTo(configurationNodeId))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(record -> record.getExamId())
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
.map(ExamConfigurationMapRecord::getExamId)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<Long>> getExamIdsForConfigId(final Long configurationId) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.configurationNodeRecordMapper.selectIdsByExample()
|
||||
return Result.tryCatch(() -> this.configurationNodeRecordMapper.selectIdsByExample()
|
||||
.leftJoin(ConfigurationRecordDynamicSqlSupport.configurationRecord)
|
||||
.on(
|
||||
ConfigurationRecordDynamicSqlSupport.configurationNodeId,
|
||||
|
@ -341,8 +336,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.collect(Utils.toSingleton());
|
||||
})
|
||||
.collect(Utils.toSingleton()))
|
||||
.flatMap(this::getExamIdsForConfigNodeId);
|
||||
}
|
||||
|
||||
|
@ -379,7 +373,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
(exam != null) ? exam.type : ExamType.UNDEFINED,
|
||||
record.getConfigurationNodeId(),
|
||||
record.getUserNames(),
|
||||
null,
|
||||
record.getEncryptSecret(),
|
||||
null,
|
||||
config.getName(),
|
||||
config.getDescription(),
|
||||
|
@ -419,8 +413,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
}
|
||||
|
||||
private Result<Collection<EntityKey>> allIdsOfInstitution(final EntityKey institutionKey) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.examConfigurationMapRecordMapper.selectIdsByExample()
|
||||
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample()
|
||||
.where(
|
||||
ExamConfigurationMapRecordDynamicSqlSupport.institutionId,
|
||||
isEqualTo(Long.valueOf(institutionKey.modelId)))
|
||||
|
@ -428,13 +421,11 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
.execute()
|
||||
.stream()
|
||||
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private Result<Collection<EntityKey>> allIdsOfLmsSetup(final EntityKey lmsSetupKey) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.examConfigurationMapRecordMapper.selectIdsByExample()
|
||||
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample()
|
||||
.leftJoin(ExamRecordDynamicSqlSupport.examRecord)
|
||||
.on(
|
||||
ExamRecordDynamicSqlSupport.id,
|
||||
|
@ -447,13 +438,11 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
.execute()
|
||||
.stream()
|
||||
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private Result<Collection<EntityKey>> allIdsOfExam(final EntityKey examKey) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.examConfigurationMapRecordMapper.selectIdsByExample()
|
||||
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample()
|
||||
.where(
|
||||
ExamConfigurationMapRecordDynamicSqlSupport.examId,
|
||||
isEqualTo(Long.valueOf(examKey.modelId)))
|
||||
|
@ -461,13 +450,11 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
.execute()
|
||||
.stream()
|
||||
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private Result<Collection<EntityKey>> allIdsOfConfig(final EntityKey configKey) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.examConfigurationMapRecordMapper.selectIdsByExample()
|
||||
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample()
|
||||
.where(
|
||||
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
|
||||
isEqualTo(Long.valueOf(configKey.modelId)))
|
||||
|
@ -475,8 +462,7 @@ public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
|
|||
.execute()
|
||||
.stream()
|
||||
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private String getEncryptionPassword(final ExamConfigurationMap examConfigurationMap) {
|
||||
|
|
|
@ -14,7 +14,9 @@ import java.util.ArrayList;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -33,6 +35,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
|||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig.ConfigPurpose;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
@ -110,9 +113,7 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
|
|||
final FilterMap filterMap,
|
||||
final Predicate<SebClientConfig> predicate) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
return this.sebClientConfigRecordMapper
|
||||
return Result.tryCatch(() -> this.sebClientConfigRecordMapper
|
||||
.selectByExample()
|
||||
.where(
|
||||
SebClientConfigRecordDynamicSqlSupport.institutionId,
|
||||
|
@ -132,15 +133,12 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
|
|||
.map(this::toDomainModel)
|
||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||
.filter(predicate)
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<SebClientConfig> byClientName(final String clientName) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
return this.sebClientConfigRecordMapper
|
||||
return Result.tryCatch(() -> this.sebClientConfigRecordMapper
|
||||
.selectByExample()
|
||||
.where(
|
||||
SebClientConfigRecordDynamicSqlSupport.clientName,
|
||||
|
@ -150,13 +148,12 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
|
|||
.stream()
|
||||
.map(this::toDomainModel)
|
||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||
.collect(Utils.toSingleton());
|
||||
});
|
||||
.collect(Utils.toSingleton()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<CharSequence> getConfigPasswortCipherByClientName(final String clientName) {
|
||||
public Result<CharSequence> getConfigPasswordCipherByClientName(final String clientName) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final SebClientConfigRecord record = this.sebClientConfigRecordMapper
|
||||
|
@ -187,8 +184,7 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
|
|||
.where(SebClientConfigRecordDynamicSqlSupport.id, isEqualTo(Long.valueOf(modelId)))
|
||||
.and(SebClientConfigRecordDynamicSqlSupport.active, isEqualTo(BooleanUtils.toInteger(true)))
|
||||
.build()
|
||||
.execute()
|
||||
.longValue() > 0;
|
||||
.execute() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -234,11 +230,7 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
|
|||
this.sebClientConfigRecordMapper
|
||||
.insert(newRecord);
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||
newRecord.getId(),
|
||||
SebClientConfig.ATTR_FALLBACK_START_URL,
|
||||
sebClientConfig.fallbackStartURL);
|
||||
saveAdditionalAttributes(sebClientConfig, newRecord.getId());
|
||||
|
||||
return newRecord;
|
||||
})
|
||||
|
@ -266,11 +258,7 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
|
|||
this.sebClientConfigRecordMapper
|
||||
.updateByPrimaryKeySelective(newRecord);
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||
newRecord.getId(),
|
||||
SebClientConfig.ATTR_FALLBACK_START_URL,
|
||||
sebClientConfig.fallbackStartURL);
|
||||
saveAdditionalAttributes(sebClientConfig, newRecord.getId());
|
||||
|
||||
return this.sebClientConfigRecordMapper
|
||||
.selectByPrimaryKey(sebClientConfig.id);
|
||||
|
@ -300,17 +288,14 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
|
|||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<SebClientConfig>> allOf(final Set<Long> pks) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
return this.sebClientConfigRecordMapper.selectByExample()
|
||||
return Result.tryCatch(() -> this.sebClientConfigRecordMapper.selectByExample()
|
||||
.where(SebClientConfigRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(this::toDomainModel)
|
||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -336,28 +321,24 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
|
|||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<CharSequence> getConfigPasswortCipher(final String modelId) {
|
||||
public Result<CharSequence> getConfigPasswordCipher(final String modelId) {
|
||||
return recordByModelId(modelId)
|
||||
.map(rec -> rec.getEncryptSecret());
|
||||
.map(SebClientConfigRecord::getEncryptSecret);
|
||||
}
|
||||
|
||||
private Result<Collection<EntityKey>> allIdsOfInstitution(final EntityKey institutionKey) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.sebClientConfigRecordMapper.selectIdsByExample()
|
||||
return Result.tryCatch(() -> this.sebClientConfigRecordMapper.selectIdsByExample()
|
||||
.where(SebClientConfigRecordDynamicSqlSupport.institutionId,
|
||||
isEqualTo(Long.valueOf(institutionKey.modelId)))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION))
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private Result<SebClientConfigRecord> recordByModelId(final String modelId) {
|
||||
return Result.tryCatch(() -> {
|
||||
return recordById(Long.parseLong(modelId)).getOrThrow();
|
||||
});
|
||||
return Result.tryCatch(() -> recordById(Long.parseLong(modelId)).getOrThrow());
|
||||
}
|
||||
|
||||
private Result<SebClientConfigRecord> recordById(final Long id) {
|
||||
|
@ -375,30 +356,57 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
|
|||
}
|
||||
|
||||
private Result<SebClientConfig> toDomainModel(final SebClientConfigRecord record) {
|
||||
final String fallbackURL = this.additionalAttributesDAO.getAdditionalAttributes(
|
||||
|
||||
Map<String, AdditionalAttributeRecord> additionalAttributes = this.additionalAttributesDAO
|
||||
.getAdditionalAttributes(
|
||||
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||
record.getId())
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.filter(rec -> SebClientConfig.ATTR_FALLBACK_START_URL.equals(rec.getName()))
|
||||
.findFirst()
|
||||
.map(AdditionalAttributeRecord::getValue)
|
||||
.orElse(null);
|
||||
.collect(Collectors.toMap(
|
||||
AdditionalAttributeRecord::getName,
|
||||
Function.identity()));
|
||||
|
||||
additionalAttributes.get(SebClientConfig.ATTR_CONFIG_PURPOSE);
|
||||
|
||||
return Result.tryCatch(() -> new SebClientConfig(
|
||||
record.getId(),
|
||||
record.getInstitutionId(),
|
||||
record.getName(),
|
||||
fallbackURL,
|
||||
record.getDate(),
|
||||
additionalAttributes.containsKey(SebClientConfig.ATTR_CONFIG_PURPOSE)
|
||||
? ConfigPurpose.valueOf(additionalAttributes.get(SebClientConfig.ATTR_CONFIG_PURPOSE).getValue())
|
||||
: ConfigPurpose.START_EXAM,
|
||||
additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK) &&
|
||||
BooleanUtils.toBoolean(additionalAttributes.get(SebClientConfig.ATTR_FALLBACK).getValue()),
|
||||
additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_START_URL)
|
||||
? additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_START_URL).getValue()
|
||||
: null,
|
||||
additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_TIMEOUT)
|
||||
? Long.parseLong(additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_TIMEOUT).getValue())
|
||||
: null,
|
||||
additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_ATTEMPTS)
|
||||
? Short.parseShort(additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_ATTEMPTS).getValue())
|
||||
: null,
|
||||
additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL)
|
||||
? Short.parseShort(additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL).getValue())
|
||||
: null,
|
||||
additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_PASSWORD)
|
||||
? additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_PASSWORD).getValue()
|
||||
: null,
|
||||
null,
|
||||
additionalAttributes.containsKey(SebClientConfig.ATTR_QUIT_PASSWORD)
|
||||
? additionalAttributes.get(SebClientConfig.ATTR_QUIT_PASSWORD).getValue()
|
||||
: null,
|
||||
null,
|
||||
record.getDate(),
|
||||
record.getEncryptSecret(),
|
||||
null,
|
||||
BooleanUtils.toBooleanObject(record.getActive())));
|
||||
}
|
||||
|
||||
private String getEncryptionPassword(final SebClientConfig sebClientConfig) {
|
||||
if (sebClientConfig.hasEncryptionSecret() &&
|
||||
!sebClientConfig.encryptSecret.equals(sebClientConfig.confirmEncryptSecret)) {
|
||||
!sebClientConfig.encryptSecret.equals(sebClientConfig.encryptSecretConfirm)) {
|
||||
throw new APIMessageException(ErrorMessage.PASSWORD_MISMATCH);
|
||||
}
|
||||
|
||||
|
@ -420,11 +428,99 @@ public class SebClientConfigDAOImpl implements SebClientConfigDAO {
|
|||
.build()
|
||||
.execute();
|
||||
|
||||
if (otherWithSameName != null && otherWithSameName.longValue() > 0) {
|
||||
if (otherWithSameName != null && otherWithSameName > 0) {
|
||||
throw new APIMessageException(APIMessage.fieldValidationError(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME,
|
||||
"clientconfig:name:name.notunique"));
|
||||
}
|
||||
}
|
||||
|
||||
private void saveAdditionalAttributes(SebClientConfig sebClientConfig, Long configId) {
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||
configId,
|
||||
SebClientConfig.ATTR_CONFIG_PURPOSE,
|
||||
(sebClientConfig.configPurpose != null)
|
||||
? sebClientConfig.configPurpose.name()
|
||||
: ConfigPurpose.START_EXAM.name());
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||
configId,
|
||||
SebClientConfig.ATTR_FALLBACK,
|
||||
String.valueOf(BooleanUtils.isTrue(sebClientConfig.fallback)));
|
||||
|
||||
if (BooleanUtils.isTrue(sebClientConfig.fallback)) {
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||
configId,
|
||||
SebClientConfig.ATTR_FALLBACK_START_URL,
|
||||
sebClientConfig.fallbackStartURL);
|
||||
} else {
|
||||
this.additionalAttributesDAO.delete(
|
||||
configId,
|
||||
SebClientConfig.ATTR_FALLBACK_START_URL);
|
||||
}
|
||||
|
||||
if (BooleanUtils.isTrue(sebClientConfig.fallback)) {
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||
configId,
|
||||
SebClientConfig.ATTR_FALLBACK_TIMEOUT,
|
||||
sebClientConfig.fallbackTimeout.toString());
|
||||
} else {
|
||||
this.additionalAttributesDAO.delete(
|
||||
configId,
|
||||
SebClientConfig.ATTR_FALLBACK_TIMEOUT);
|
||||
}
|
||||
|
||||
if (BooleanUtils.isTrue(sebClientConfig.fallback)) {
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||
configId,
|
||||
SebClientConfig.ATTR_FALLBACK_ATTEMPTS,
|
||||
sebClientConfig.fallbackAttempts.toString());
|
||||
} else {
|
||||
this.additionalAttributesDAO.delete(
|
||||
configId,
|
||||
SebClientConfig.ATTR_FALLBACK_ATTEMPTS);
|
||||
}
|
||||
|
||||
if (BooleanUtils.isTrue(sebClientConfig.fallback)) {
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||
configId,
|
||||
SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL,
|
||||
sebClientConfig.fallbackAttemptInterval.toString());
|
||||
} else {
|
||||
this.additionalAttributesDAO.delete(
|
||||
configId,
|
||||
SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL);
|
||||
}
|
||||
|
||||
if (BooleanUtils.isTrue(sebClientConfig.fallback) && StringUtils.isNotBlank(sebClientConfig.fallbackPassword)) {
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||
configId,
|
||||
SebClientConfig.ATTR_FALLBACK_PASSWORD,
|
||||
this.clientCredentialService.encrypt(sebClientConfig.fallbackPassword).toString());
|
||||
} else {
|
||||
this.additionalAttributesDAO.delete(
|
||||
configId,
|
||||
SebClientConfig.ATTR_FALLBACK_PASSWORD);
|
||||
}
|
||||
|
||||
if (BooleanUtils.isTrue(sebClientConfig.fallback) && StringUtils.isNotBlank(sebClientConfig.quitPassword)) {
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||
configId,
|
||||
SebClientConfig.ATTR_QUIT_PASSWORD,
|
||||
this.clientCredentialService.encrypt(sebClientConfig.quitPassword).toString());
|
||||
} else {
|
||||
this.additionalAttributesDAO.delete(
|
||||
configId,
|
||||
SebClientConfig.ATTR_QUIT_PASSWORD);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,31 +25,7 @@ public interface ClientConfigService {
|
|||
|
||||
Logger log = LoggerFactory.getLogger(ClientConfigService.class);
|
||||
|
||||
public static final String EXAM_CLIENT_DETAILS_CACHE = "EXAM_CLIENT_DETAILS_CACHE";
|
||||
|
||||
static String SEB_CLIENT_CONFIG_EXAMPLE_XML =
|
||||
" <dict>\r\n" +
|
||||
" <key>sebMode</key>\r\n" +
|
||||
" <integer>1</integer>\r\n" +
|
||||
" <key>sebConfigPurpose</key>\r\n" +
|
||||
" <integer>1</integer>\r\n" +
|
||||
" <key>sebServerFallback</key>\r\n" +
|
||||
" <%s />\r\n" +
|
||||
" %s" +
|
||||
" <key>sebServerURL</key>\r\n" +
|
||||
" <string>%s</string>\r\n" +
|
||||
" <key>sebServerConfiguration</key>\r\n" +
|
||||
" <dict>\r\n" +
|
||||
" <key>institution</key>\r\n" +
|
||||
" <string>%s</string>\r\n" +
|
||||
" <key>clientName</key>\r\n" +
|
||||
" <string>%s</string>\r\n" +
|
||||
" <key>clientSecret</key>\r\n" +
|
||||
" <string>%s</string>\r\n" +
|
||||
" <key>apiDiscovery</key>\r\n" +
|
||||
" <string>%s</string>\r\n" +
|
||||
" </dict>\r\n" +
|
||||
" </dict>\r\n";
|
||||
String EXAM_CLIENT_DETAILS_CACHE = "EXAM_CLIENT_DETAILS_CACHE";
|
||||
|
||||
/** Indicates if there is any SebClientConfiguration for a specified institution.
|
||||
*
|
||||
|
|
|
@ -8,29 +8,6 @@
|
|||
|
||||
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.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
|
||||
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.WebSecurityConfig;
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
|
||||
|
@ -54,6 +31,29 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncrypti
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebConfigEncryptionServiceImpl.EncryptionContext;
|
||||
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebserviceResourceConfiguration;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.provider.ClientDetails;
|
||||
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
|
||||
import org.springframework.security.oauth2.provider.token.TokenStore;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
|
@ -62,6 +62,38 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(ClientConfigServiceImpl.class);
|
||||
|
||||
private static final String SEB_CLIENT_CONFIG_TEMPLATE_XML =
|
||||
" <dict>\r\n" +
|
||||
" <key>sebMode</key>\r\n" +
|
||||
" <integer>1</integer>\r\n" +
|
||||
" <key>sebConfigPurpose</key>\r\n" +
|
||||
" <integer>%s</integer>\r\n" +
|
||||
" <key>sebServerFallback</key>\r\n" +
|
||||
" <%s />\r\n" +
|
||||
"%s" +
|
||||
" <key>sebServerURL</key>\r\n" +
|
||||
" <string>%s</string>\r\n" +
|
||||
" <key>sebServerConfiguration</key>\r\n" +
|
||||
" <dict>\r\n" +
|
||||
" <key>institution</key>\r\n" +
|
||||
" <string>%s</string>\r\n" +
|
||||
" <key>clientName</key>\r\n" +
|
||||
" <string>%s</string>\r\n" +
|
||||
" <key>clientSecret</key>\r\n" +
|
||||
" <string>%s</string>\r\n" +
|
||||
" <key>apiDiscovery</key>\r\n" +
|
||||
" <string>%s</string>\r\n" +
|
||||
" </dict>\r\n" +
|
||||
" </dict>\r\n";
|
||||
|
||||
private final static String SEB_CLIENT_CONFIG_INTEGER_TEMPLATE =
|
||||
" <key>%s</key>\r\n" +
|
||||
" <integer>%s</integer>\r\n";
|
||||
|
||||
private final static String SEB_CLIENT_CONFIG_STRING_TEMPLATE =
|
||||
" <key>%s</key>\r\n" +
|
||||
" <string>%s</string>\r\n";
|
||||
|
||||
private final InstitutionDAO institutionDAO;
|
||||
private final SebClientConfigDAO sebClientConfigDAO;
|
||||
private final ClientCredentialService clientCredentialService;
|
||||
|
@ -109,6 +141,15 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
|||
institutionId,
|
||||
institution.name + "_" + UUID.randomUUID(),
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
|
@ -150,10 +191,10 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
|||
.byModelId(modelId).getOrThrow();
|
||||
|
||||
final CharSequence encryptionPassword = this.sebClientConfigDAO
|
||||
.getConfigPasswortCipher(config.getModelId())
|
||||
.getConfigPasswordCipher(config.getModelId())
|
||||
.getOr(null);
|
||||
|
||||
final String plainTextConfig = getPlainXMLConfig(config);
|
||||
final String plainTextXMLContent = extractXMLContent(config);
|
||||
|
||||
PipedOutputStream pOut = null;
|
||||
PipedInputStream pIn = null;
|
||||
|
@ -165,7 +206,7 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
|||
Constants.XML_VERSION_HEADER +
|
||||
Constants.XML_DOCTYPE_HEADER +
|
||||
Constants.XML_PLIST_START_V1 +
|
||||
plainTextConfig +
|
||||
plainTextXMLContent +
|
||||
Constants.XML_PLIST_END,
|
||||
StandardCharsets.UTF_8.name());
|
||||
|
||||
|
@ -206,39 +247,63 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
|||
}
|
||||
}
|
||||
|
||||
public String getPlainXMLConfig(final SebClientConfig config) {
|
||||
private String extractXMLContent(final SebClientConfig config) {
|
||||
|
||||
String fallbackAddition = "";
|
||||
if (BooleanUtils.isTrue(config.fallback)) {
|
||||
fallbackAddition += String.format(
|
||||
SEB_CLIENT_CONFIG_STRING_TEMPLATE,
|
||||
SebClientConfig.ATTR_FALLBACK_START_URL,
|
||||
config.fallbackStartURL);
|
||||
|
||||
fallbackAddition += String.format(
|
||||
SEB_CLIENT_CONFIG_INTEGER_TEMPLATE,
|
||||
SebClientConfig.ATTR_FALLBACK_TIMEOUT,
|
||||
config.fallbackTimeout);
|
||||
|
||||
fallbackAddition += String.format(
|
||||
SEB_CLIENT_CONFIG_INTEGER_TEMPLATE,
|
||||
SebClientConfig.ATTR_FALLBACK_ATTEMPTS,
|
||||
config.fallbackAttempts);
|
||||
|
||||
fallbackAddition += String.format(
|
||||
SEB_CLIENT_CONFIG_INTEGER_TEMPLATE,
|
||||
SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL,
|
||||
config.fallbackAttemptInterval);
|
||||
|
||||
if (StringUtils.isNotBlank(config.fallbackPassword)) {
|
||||
CharSequence decrypt = clientCredentialService.decrypt(config.fallbackPassword);
|
||||
fallbackAddition += String.format(
|
||||
SEB_CLIENT_CONFIG_STRING_TEMPLATE,
|
||||
SebClientConfig.ATTR_FALLBACK_PASSWORD,
|
||||
Utils.hash_SHA_256_Base_16(decrypt));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(config.quitPassword)) {
|
||||
CharSequence decrypt = clientCredentialService.decrypt(config.quitPassword);
|
||||
fallbackAddition += String.format(
|
||||
SEB_CLIENT_CONFIG_STRING_TEMPLATE,
|
||||
SebClientConfig.ATTR_QUIT_PASSWORD,
|
||||
Utils.hash_SHA_256_Base_16(decrypt));
|
||||
}
|
||||
}
|
||||
|
||||
final ClientCredentials sebClientCredentials = this.sebClientConfigDAO
|
||||
.getSebClientCredentials(config.getModelId())
|
||||
.getOrThrow();
|
||||
|
||||
final CharSequence plainClientId = sebClientCredentials.clientId;
|
||||
final CharSequence plainClientSecret = this.clientCredentialService
|
||||
.getPlainClientSecret(sebClientCredentials);
|
||||
|
||||
final String plainTextConfig = extractXML(
|
||||
config,
|
||||
plainClientId,
|
||||
plainClientSecret);
|
||||
|
||||
return plainTextConfig;
|
||||
}
|
||||
|
||||
private String extractXML(
|
||||
final SebClientConfig config,
|
||||
final CharSequence plainClientId,
|
||||
final CharSequence plainClientSecret) {
|
||||
|
||||
final String plainTextConfig = String.format(
|
||||
SEB_CLIENT_CONFIG_EXAMPLE_XML,
|
||||
SEB_CLIENT_CONFIG_TEMPLATE_XML,
|
||||
config.configPurpose.ordinal(),
|
||||
(StringUtils.isNotBlank(config.fallbackStartURL))
|
||||
? "true"
|
||||
: "false",
|
||||
(StringUtils.isNotBlank(config.fallbackStartURL))
|
||||
? "<key>startURL</key>\r\n <string>" + config.fallbackStartURL + "</string>\r\n"
|
||||
: "",
|
||||
fallbackAddition,
|
||||
this.webserviceInfo.getExternalServerURL(),
|
||||
String.valueOf(config.institutionId),
|
||||
config.institutionId,
|
||||
plainClientId,
|
||||
plainClientSecret,
|
||||
this.webserviceInfo.getDiscoveryEndpoint());
|
||||
|
@ -259,7 +324,6 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
|||
bulkAction.type == BulkActionType.HARD_DELETE) {
|
||||
|
||||
bulkAction.extractKeys(EntityType.SEB_CLIENT_CONFIGURATION)
|
||||
.stream()
|
||||
.forEach(this::flushClientConfigData);
|
||||
}
|
||||
|
||||
|
@ -275,8 +339,7 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
|||
.clientIdAsString();
|
||||
|
||||
final Collection<OAuth2AccessToken> tokensByClientId = this.tokenStore.findTokensByClientId(clientName);
|
||||
tokensByClientId.stream()
|
||||
.forEach(token -> this.tokenStore.removeAccessToken(token));
|
||||
tokensByClientId.forEach(this.tokenStore::removeAccessToken);
|
||||
} catch (final Exception e) {
|
||||
log.error("Unexpected error while trying to flush ClientConfig data for {}", key, e);
|
||||
}
|
||||
|
@ -307,7 +370,7 @@ public class ClientConfigServiceImpl implements ClientConfigService {
|
|||
* @param clientId the clientId/clientName
|
||||
* @return encoded clientSecret for that SebClientConfiguration with clientId or null of not existing */
|
||||
private Result<CharSequence> getEncodedClientConfigSecret(final String clientId) {
|
||||
return this.sebClientConfigDAO.getConfigPasswortCipherByClientName(clientId)
|
||||
return this.sebClientConfigDAO.getConfigPasswordCipherByClientName(clientId)
|
||||
.map(cipher -> this.clientPasswordEncoder.encode(this.clientCredentialService.decrypt(cipher)));
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import javax.xml.parsers.ParserConfigurationException;
|
|||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import org.apache.tomcat.util.http.fileupload.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -72,19 +73,22 @@ public class ExamConfigIO {
|
|||
private final ConfigurationDAO configurationDAO;
|
||||
private final AttributeValueConverterService attributeValueConverterService;
|
||||
private final ZipService zipService;
|
||||
private final Cryptor cryptor;
|
||||
|
||||
protected ExamConfigIO(
|
||||
final ConfigurationAttributeDAO configurationAttributeDAO,
|
||||
final ConfigurationValueDAO configurationValueDAO,
|
||||
final ConfigurationDAO configurationDAO,
|
||||
final AttributeValueConverterService attributeValueConverterService,
|
||||
final ZipService zipService) {
|
||||
final ZipService zipService,
|
||||
final Cryptor cryptor) {
|
||||
|
||||
this.configurationAttributeDAO = configurationAttributeDAO;
|
||||
this.configurationValueDAO = configurationValueDAO;
|
||||
this.configurationDAO = configurationDAO;
|
||||
this.attributeValueConverterService = attributeValueConverterService;
|
||||
this.zipService = zipService;
|
||||
this.cryptor = cryptor;
|
||||
}
|
||||
|
||||
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
|
||||
|
@ -188,6 +192,7 @@ public class ExamConfigIO {
|
|||
// the SAX handler with a ConfigValue sink that saves the values to DB
|
||||
// and a attribute-name/id mapping function with pre-created mapping
|
||||
final ExamConfigXMLParser examConfigImportHandler = new ExamConfigXMLParser(
|
||||
cryptor,
|
||||
institutionId,
|
||||
configurationId,
|
||||
value -> this.configurationValueDAO
|
||||
|
|
|
@ -174,7 +174,7 @@ public class ExamConfigServiceImpl implements ExamConfigService {
|
|||
final Long configurationNodeId) {
|
||||
|
||||
final CharSequence passwordCipher = this.examConfigurationMapDAO
|
||||
.getConfigPasswortCipher(examId, configurationNodeId)
|
||||
.getConfigPasswordCipher(examId, configurationNodeId)
|
||||
.getOr(null);
|
||||
|
||||
if (StringUtils.isNotBlank(passwordCipher)) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import java.util.Stack;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -85,6 +86,14 @@ public class ExamConfigXMLParser extends DefaultHandler {
|
|||
private static final Set<String> KNOWN_INLINE_TABLES = new HashSet<>(Arrays.asList(
|
||||
"arguments"));
|
||||
|
||||
public static final Set<String> PASSWORD_ATTRIBUTES = new HashSet<>(Arrays.asList(
|
||||
"hashedQuitPassword",
|
||||
"hashedAdminPassword"
|
||||
));
|
||||
|
||||
public static final String IMPORTED_PASSWORD_MARKER = "_IMPORTED_PASSWORD";
|
||||
|
||||
private final Cryptor cryptor;
|
||||
private final Consumer<ConfigurationValue> valueConsumer;
|
||||
private final Function<String, ConfigurationAttribute> attributeResolver;
|
||||
private final Long institutionId;
|
||||
|
@ -96,12 +105,14 @@ public class ExamConfigXMLParser extends DefaultHandler {
|
|||
private Boolean createNewDesktop = null;
|
||||
|
||||
public ExamConfigXMLParser(
|
||||
final Cryptor cryptor,
|
||||
final Long institutionId,
|
||||
final Long configId,
|
||||
final Consumer<ConfigurationValue> valueConsumer,
|
||||
final Function<String, ConfigurationAttribute> attributeResolver) {
|
||||
|
||||
super();
|
||||
this.cryptor = cryptor;
|
||||
this.valueConsumer = valueConsumer;
|
||||
this.attributeResolver = attributeResolver;
|
||||
this.institutionId = institutionId;
|
||||
|
@ -430,6 +441,21 @@ public class ExamConfigXMLParser extends DefaultHandler {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (PASSWORD_ATTRIBUTES.contains(name)) {
|
||||
// NOTE this is a special case, if a hashed password is imported it is not possible to view this password
|
||||
// later in plain text to the administrator. Therefore this password hash is marked here as imported
|
||||
// and internally encrypted as usual. So the password will be decrypted while viewing and is recognizable
|
||||
// for the export so that the password can be decrypted with internal encryption and then, if import
|
||||
// marked, just send to the export by removing the marker and do not rehash the already hashed password.
|
||||
return new ConfigurationValue(
|
||||
null,
|
||||
this.institutionId,
|
||||
this.configId,
|
||||
attribute.id,
|
||||
listIndex,
|
||||
StringUtils.isNotBlank(value) ? cryptor.encrypt(value + IMPORTED_PASSWORD_MARKER).toString() : value);
|
||||
}
|
||||
|
||||
return new ConfigurationValue(
|
||||
null,
|
||||
this.institutionId,
|
||||
|
|
|
@ -16,6 +16,8 @@ import java.util.HashSet;
|
|||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.ExamConfigXMLParser;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -40,12 +42,20 @@ public class StringConverter implements AttributeValueConverter {
|
|||
AttributeType.DECIMAL,
|
||||
AttributeType.COMBO_SELECTION)));
|
||||
|
||||
|
||||
|
||||
private static final String XML_TEMPLATE = "<key>%s</key><string>%s</string>";
|
||||
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 final ClientCredentialService clientCredentialService;
|
||||
|
||||
public StringConverter(final ClientCredentialService clientCredentialService) {
|
||||
this.clientCredentialService = clientCredentialService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<AttributeType> types() {
|
||||
return SUPPORTED_TYPES;
|
||||
|
@ -85,15 +95,38 @@ public class StringConverter implements AttributeValueConverter {
|
|||
final String emptyTemplate) throws IOException {
|
||||
|
||||
final String val = (value != null && value.value != null) ? value.value : attribute.getDefaultValue();
|
||||
String realName = AttributeValueConverter.extractName(attribute);
|
||||
if (StringUtils.isNotBlank(val)) {
|
||||
out.write(Utils.toByteArray(String.format(
|
||||
template,
|
||||
AttributeValueConverter.extractName(attribute),
|
||||
val)));
|
||||
realName,
|
||||
convertPassword(realName, val))));
|
||||
} else {
|
||||
out.write(Utils.toByteArray(String.format(
|
||||
emptyTemplate,
|
||||
AttributeValueConverter.extractName(attribute))));
|
||||
realName)));
|
||||
}
|
||||
}
|
||||
|
||||
private CharSequence convertPassword(
|
||||
final String attributeName,
|
||||
final String value) {
|
||||
|
||||
if (StringUtils.isBlank(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!ExamConfigXMLParser.PASSWORD_ATTRIBUTES.contains(attributeName)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// decrypt internally encrypted password and hash it for export
|
||||
// NOTE: see special case description in ExamConfigXMLParser.createConfigurationValue
|
||||
String plainText = this.clientCredentialService.decrypt(value).toString();
|
||||
if (plainText.endsWith(ExamConfigXMLParser.IMPORTED_PASSWORD_MARKER)) {
|
||||
return plainText.replace(ExamConfigXMLParser.IMPORTED_PASSWORD_MARKER, StringUtils.EMPTY);
|
||||
} else {
|
||||
return Utils.hash_SHA_256_Base_16(plainText);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.util.function.Function;
|
|||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import org.apache.tomcat.util.http.fileupload.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -37,6 +38,12 @@ public class XMLAttributeLoader {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(XMLAttributeLoader.class);
|
||||
|
||||
private final Cryptor cryptor;
|
||||
|
||||
public XMLAttributeLoader(Cryptor cryptor) {
|
||||
this.cryptor = cryptor;
|
||||
}
|
||||
|
||||
public Collection<ConfigurationValue> loadFromXML(
|
||||
final Long institutionId,
|
||||
final Long configurationId,
|
||||
|
@ -57,6 +64,7 @@ public class XMLAttributeLoader {
|
|||
final Collection<ConfigurationValue> values = new ArrayList<>();
|
||||
|
||||
final ExamConfigXMLParser examConfigImportHandler = new ExamConfigXMLParser(
|
||||
cryptor,
|
||||
institutionId,
|
||||
configurationId,
|
||||
values::add,
|
||||
|
|
|
@ -270,13 +270,17 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
|||
final Result<Configuration> doImport = doImport(password, request, followup);
|
||||
if (doImport.hasError()) {
|
||||
|
||||
// rollback of the new configuration
|
||||
// rollback if the new configuration
|
||||
this.configurationNodeDAO.delete(new HashSet<>(Arrays.asList(new EntityKey(
|
||||
followup.configurationNodeId,
|
||||
EntityType.CONFIGURATION_NODE))));
|
||||
}
|
||||
|
||||
return doImport
|
||||
Configuration config = doImport
|
||||
.getOrThrow();
|
||||
|
||||
return this.configurationDAO
|
||||
.saveToHistory(config.configurationNodeId)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
|
|
|
@ -11,11 +11,15 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
|||
import java.io.IOException;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.mybatis.dynamic.sql.SqlTable;
|
||||
|
@ -107,14 +111,6 @@ public class SebClientConfigController extends ActivatableEntityController<SebCl
|
|||
outputStream.flush();
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
// final StreamingResponseBody stream = out -> {
|
||||
// this.sebClientConfigService.exportSebClientConfiguration(
|
||||
// out,
|
||||
// modelId);
|
||||
// };
|
||||
//
|
||||
// return new ResponseEntity<>(stream, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -152,14 +148,67 @@ public class SebClientConfigController extends ActivatableEntityController<SebCl
|
|||
}
|
||||
|
||||
private SebClientConfig checkPasswordMatch(final SebClientConfig entity) {
|
||||
if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.confirmEncryptSecret)) {
|
||||
throw new APIMessageException(APIMessage.fieldValidationError(
|
||||
Collection<APIMessage> errors = new ArrayList<>();
|
||||
if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.encryptSecretConfirm)) {
|
||||
errors.add(APIMessage.fieldValidationError(
|
||||
new FieldError(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
|
||||
PasswordChange.ATTR_NAME_PASSWORD,
|
||||
"clientConfig:confirm_encrypt_secret:password.mismatch")));
|
||||
}
|
||||
|
||||
if (entity.hasFallbackPassword() && !entity.fallbackPassword.equals(entity.fallbackPasswordConfirm)) {
|
||||
errors.add(APIMessage.fieldValidationError(
|
||||
new FieldError(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
|
||||
SebClientConfig.ATTR_FALLBACK_PASSWORD_CONFIRM,
|
||||
"clientConfig:sebServerFallbackPasswordHashConfirm:password.mismatch")));
|
||||
}
|
||||
|
||||
if (entity.hasQuitPassword() && !entity.quitPassword.equals(entity.quitPasswordConfirm)) {
|
||||
errors.add(APIMessage.fieldValidationError(
|
||||
new FieldError(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
|
||||
SebClientConfig.ATTR_QUIT_PASSWORD_CONFIRM,
|
||||
"clientConfig:hashedQuitPasswordConfirm:password.mismatch")));
|
||||
}
|
||||
|
||||
if (BooleanUtils.isTrue(entity.fallback) && StringUtils.isBlank(entity.fallbackStartURL)) {
|
||||
errors.add(APIMessage.fieldValidationError(
|
||||
new FieldError(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
|
||||
SebClientConfig.ATTR_FALLBACK_START_URL,
|
||||
"clientConfig:startURL:notNull")));
|
||||
}
|
||||
|
||||
if (BooleanUtils.isTrue(entity.fallback) && entity.fallbackTimeout == null) {
|
||||
errors.add(APIMessage.fieldValidationError(
|
||||
new FieldError(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
|
||||
SebClientConfig.ATTR_FALLBACK_TIMEOUT,
|
||||
"clientConfig:sebServerFallbackTimeout:notNull")));
|
||||
}
|
||||
|
||||
if (BooleanUtils.isTrue(entity.fallback) && entity.fallbackAttempts == null) {
|
||||
errors.add(APIMessage.fieldValidationError(
|
||||
new FieldError(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
|
||||
SebClientConfig.ATTR_FALLBACK_ATTEMPTS,
|
||||
"clientConfig:sebServerFallbackAttempts:notNull")));
|
||||
}
|
||||
|
||||
if (BooleanUtils.isTrue(entity.fallback) && entity.fallbackAttemptInterval == null) {
|
||||
errors.add(APIMessage.fieldValidationError(
|
||||
new FieldError(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
|
||||
SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL,
|
||||
"clientConfig:sebServerFallbackAttemptInterval:notNull")));
|
||||
}
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
throw new APIMessage.APIMessageException(errors);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,8 @@ sebserver.overall.action.goAwayFromEditPageConfirm=Are you sure you want to leav
|
|||
sebserver.overall.action.category.varia=
|
||||
sebserver.overall.action.category.filter=
|
||||
|
||||
sebserver.overall.action.showPassword.tooltip=Show / hide password in plain text.
|
||||
|
||||
sebserver.overall.status.active=Active
|
||||
sebserver.overall.status.inactive=Inactive
|
||||
sebserver.overall.status.all=All
|
||||
|
@ -567,7 +569,7 @@ sebserver.exam.indicator.thresholds.list.add=Add a new threshold
|
|||
sebserver.exam.indicator.thresholds.list.remove=Delete this threshold
|
||||
|
||||
################################
|
||||
# SEB Client Configuration
|
||||
# SEB client configuration
|
||||
################################
|
||||
|
||||
sebserver.sebconfig.activity.name=SEB Configuration
|
||||
|
@ -576,7 +578,7 @@ sebserver.clientconfig.action.list=Client Configuration
|
|||
sebserver.clientconfig.action.export=Export
|
||||
|
||||
sebserver.clientconfig.list.empty=There is currently no SEB-Client configuration available. Please create a new one
|
||||
sebserver.clientconfig.list.title=SEB Client Configurations
|
||||
sebserver.clientconfig.list.title=SEB client configurations
|
||||
sebserver.clientconfig.list.actions=
|
||||
sebserver.clientconfig.list.column.institution=Institution
|
||||
sebserver.clientconfig.list.column.institution.tooltip=The institution of the SEB client configuration.<br/><br/>Use the filter above to specify the institution.<br/>{0}
|
||||
|
@ -587,24 +589,31 @@ sebserver.clientconfig.list.column.date.tooltip=The date when the SEB client con
|
|||
sebserver.clientconfig.list.column.active=Active
|
||||
sebserver.clientconfig.list.column.active.tooltip=The activity of SEB client configuration.<br/><br/>Use the filter above to specify the activity.<br/>{0}
|
||||
sebserver.clientconfig.info.pleaseSelect=Please select first a Client Configuration from the list
|
||||
sebserver.clientconfig.list.action.no.modify.privilege=No Access: A SEB Client Configuration from other institution cannot be modified.
|
||||
sebserver.clientconfig.list.action.no.modify.privilege=No Access: A SEB client configuration from other institution cannot be modified.
|
||||
|
||||
sebserver.clientconfig.form.title.new=Add Client Configuration
|
||||
sebserver.clientconfig.form.title=SEB Client Configuration
|
||||
sebserver.clientconfig.form.title=SEB client configuration
|
||||
sebserver.clientconfig.form.name=Name
|
||||
sebserver.clientconfig.form.name.tooltip=The name of the SEB Client Configuration.<br/>Can be any name that not already exists for another SEB Client Configuration
|
||||
sebserver.clientconfig.form.name.tooltip=The name of the SEB client configuration.<br/>Can be any name that not already exists for another SEB client configuration
|
||||
sebserver.clientconfig.form.fallback=With Fallback
|
||||
sebserver.clientconfig.form.fallback.tooltip=Indicates whether this SEB Client Configuration has a fallback definition or not
|
||||
sebserver.clientconfig.form.fallback.tooltip=Indicates whether this SEB client configuration has a fallback definition or not
|
||||
sebserver.clientconfig.form.fallback-url=Fallback Start URL
|
||||
sebserver.clientconfig.form.fallback-url.tooltip=A fallback URL that tells the SEB where to go when the SEB Server service is unavailable.
|
||||
sebserver.clientconfig.form.sebServerFallbackTimeout=Fallback Timeout
|
||||
sebserver.clientconfig.form.sebServerFallbackTimeout.tooltip=Defines the fallback timeout for the SEB Client in milli-seconds.
|
||||
sebserver.clientconfig.form.sebServerFallbackAttempts=Fallback Attempts
|
||||
sebserver.clientconfig.form.sebServerFallbackAttempts.tooltip=The number of connection attempts a SEB Client is trying before switching to fallback case.
|
||||
sebserver.clientconfig.form.sebServerFallbackAttemptInterval=Attempt Interval
|
||||
sebserver.clientconfig.form.sebServerFallbackAttemptInterval.tooltip=The interval (in milli-seconds) between connection attempts a SEB Client shall use.
|
||||
sebserver.clientconfig.form.sebServerFallbackTimeout.tooltip=Defines the fallback timeout for the SEB client in milli-seconds.
|
||||
sebserver.clientconfig.form.sebServerFallbackAttempts=Connection Attempts
|
||||
sebserver.clientconfig.form.sebServerFallbackAttempts.tooltip=The number of connection attempts a SEB client is trying before switching to fallback case.
|
||||
sebserver.clientconfig.form.sebServerFallbackAttemptInterval=Interval
|
||||
sebserver.clientconfig.form.sebServerFallbackAttemptInterval.tooltip=The interval (in milli-seconds) between connection attempts a SEB client shall use.
|
||||
sebserver.clientconfig.form.sebServerFallbackPasswordHash=Fallback Password
|
||||
sebserver.clientconfig.form.sebServerFallbackPasswordHash.tooltip=A password if set, a SEB Client user must give before the SEB Client starts the fallback procedure.
|
||||
sebserver.clientconfig.form.sebServerFallbackPasswordHash.tooltip=A password if set a SEB Client user must provide before the SEB client starts the fallback procedure.
|
||||
sebserver.clientconfig.form.sebServerFallbackPasswordHash.confirm=Confirm Fallback Password
|
||||
sebserver.clientconfig.form.sebServerFallbackPasswordHash.tooltip.confirm=Please confirm the fallback password
|
||||
sebserver.clientconfig.form.hashedQuitPassword=Quit Password
|
||||
sebserver.clientconfig.form.hashedQuitPassword.tooltip=A password if set a SEB client user must provide to be able to quit the SEB client.
|
||||
sebserver.clientconfig.form.hashedQuitPassword.confirm=Confirm Quit Password
|
||||
sebserver.clientconfig.form.hashedQuitPassword.tooltip.confirm=Please confirm the quit password
|
||||
|
||||
sebserver.clientconfig.form.date=Creation Date
|
||||
sebserver.clientconfig.form.date.tooltip=The date when the SEB client configuration was first created.
|
||||
sebserver.clientconfig.form.encryptSecret=Configuration Password
|
||||
|
@ -614,6 +623,11 @@ sebserver.clientconfig.form.encryptSecret.confirm.tooltip=Please retype the give
|
|||
sebserver.clientconfig.form.sebConfigPurpose=Configuration Purpose
|
||||
sebserver.clientconfig.form.sebConfigPurpose.tooltip=This indicates whether this client configuration shall be used to configure the SEB Client or to start an exam
|
||||
|
||||
sebserver.clientconfig.config.purpose.START_EXAM=Starting an Exam
|
||||
sebserver.clientconfig.config.purpose.START_EXAM.tooltip=If the SEB client configuration is loaded via a SEB-Link, the local configuration will not be overwritten.
|
||||
sebserver.clientconfig.config.purpose.CONFIGURE_CLIENT=Configure a Client
|
||||
sebserver.clientconfig.config.purpose.CONFIGURE_CLIENT.tooltip=If the SEB client configuration is loaded via a SEB-Link, the local configuration will be overwritten by this configuration.
|
||||
|
||||
sebserver.clientconfig.action.list.new=Add Configuration
|
||||
sebserver.clientconfig.action.list.view=View Configuration
|
||||
sebserver.clientconfig.action.list.modify=Edit Configuration
|
||||
|
|
|
@ -320,6 +320,26 @@ Text[MULTI][BORDER]:read-only.inputreadonly {
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
Text.pwdplain,
|
||||
Text:disabled.pwdplain,
|
||||
Text:read-only.pwdplain,
|
||||
Text[BORDER]:disabled.pwdplain,
|
||||
Text[BORDER]:read-only.pwdplain {
|
||||
font: 12px Arial, Helvetica, sans-serif;
|
||||
border: 1px solid #aaaaaa;
|
||||
border-radius: 0;
|
||||
padding: 3px 10px 3px 10px;
|
||||
color: #aaaaaa;
|
||||
background-repeat: repeat;
|
||||
background-position: left top;
|
||||
background-color: #ffffff;
|
||||
background-image: none;
|
||||
text-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Text:disabled,
|
||||
Text:read-only,
|
||||
Text[MULTI]:disabled,
|
||||
|
|
BIN
src/main/resources/static/images/visibility.png
Normal file
BIN
src/main/resources/static/images/visibility.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 255 B |
BIN
src/main/resources/static/images/visibility_off.png
Normal file
BIN
src/main/resources/static/images/visibility_off.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 290 B |
|
@ -34,6 +34,7 @@ public class ClientConfigTest extends GuiIntegrationTest {
|
|||
|
||||
final Result<SebClientConfig> call = restService.getBuilder(NewClientConfig.class)
|
||||
.withQueryParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config")
|
||||
.withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
|
||||
.call();
|
||||
|
||||
assertNotNull(call);
|
||||
|
@ -50,6 +51,7 @@ public class ClientConfigTest extends GuiIntegrationTest {
|
|||
|
||||
final Result<SebClientConfig> call = restService.getBuilder(NewClientConfig.class)
|
||||
.withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config")
|
||||
.withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
|
||||
.call();
|
||||
|
||||
assertNotNull(call);
|
||||
|
@ -72,6 +74,7 @@ public class ClientConfigTest extends GuiIntegrationTest {
|
|||
// create one
|
||||
final SebClientConfig config = restService.getBuilder(NewClientConfig.class)
|
||||
.withQueryParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config")
|
||||
.withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
|
@ -104,6 +107,15 @@ public class ClientConfigTest extends GuiIntegrationTest {
|
|||
config.id,
|
||||
config.institutionId,
|
||||
"new client config",
|
||||
SebClientConfig.ConfigPurpose.START_EXAM,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"password",
|
||||
|
@ -122,6 +134,15 @@ public class ClientConfigTest extends GuiIntegrationTest {
|
|||
config.id,
|
||||
config.institutionId,
|
||||
"new client config",
|
||||
SebClientConfig.ConfigPurpose.START_EXAM,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"password",
|
||||
|
@ -133,7 +154,7 @@ public class ClientConfigTest extends GuiIntegrationTest {
|
|||
assertEquals(config.id, newConfig.id);
|
||||
assertEquals("new client config", newConfig.name);
|
||||
assertTrue(newConfig.active);
|
||||
assertNull(newConfig.getEncryptSecret());
|
||||
assertNotNull(newConfig.getEncryptSecret());
|
||||
|
||||
// deactivate
|
||||
final EntityProcessingReport deactivationReport = restService.getBuilder(DeactivateClientConfig.class)
|
||||
|
|
|
@ -12,6 +12,7 @@ import static org.junit.Assert.*;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
@ -853,7 +854,12 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
|||
final Result<SebClientConfig> newConfigResponse = restService
|
||||
.getBuilder(NewClientConfig.class)
|
||||
.withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "No Password Protection")
|
||||
.withFormParam(SebClientConfig.ATTR_FALLBACK, Constants.TRUE_STRING)
|
||||
.withFormParam(SebClientConfig.ATTR_FALLBACK_START_URL, "http://fallback.com/fallback")
|
||||
.withFormParam(SebClientConfig.ATTR_FALLBACK_TIMEOUT, "100")
|
||||
.withFormParam(SebClientConfig.ATTR_FALLBACK_ATTEMPTS, "5")
|
||||
.withFormParam(SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL, "5")
|
||||
.withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
|
||||
.call();
|
||||
|
||||
assertNotNull(newConfigResponse);
|
||||
|
@ -886,9 +892,14 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
|||
final Result<SebClientConfig> configWithPasswordResponse = restService
|
||||
.getBuilder(NewClientConfig.class)
|
||||
.withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "With Password Protection")
|
||||
.withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
|
||||
.withFormParam(SebClientConfig.ATTR_FALLBACK, Constants.TRUE_STRING)
|
||||
.withFormParam(SebClientConfig.ATTR_FALLBACK_START_URL, "http://fallback.com/fallback")
|
||||
.withFormParam(SebClientConfig.ATTR_FALLBACK_TIMEOUT, "100")
|
||||
.withFormParam(SebClientConfig.ATTR_FALLBACK_ATTEMPTS, "5")
|
||||
.withFormParam(SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL, "5")
|
||||
.withFormParam(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET, "123")
|
||||
.withFormParam(SebClientConfig.ATTR_CONFIRM_ENCRYPT_SECRET, "123")
|
||||
.withFormParam(SebClientConfig.ATTR_ENCRYPT_SECRET_CONFIRM, "123")
|
||||
.call();
|
||||
|
||||
assertNotNull(configWithPasswordResponse);
|
||||
|
@ -1091,7 +1102,10 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
|||
restService);
|
||||
|
||||
// update a value -- grab first
|
||||
final ConfigurationValue value = values.get(0);
|
||||
ConfigurationValue value = values.get(0);
|
||||
if (value.attributeId == 1) {
|
||||
value = values.get(1);
|
||||
}
|
||||
ConfigurationValue newValue = new ConfigurationValue(
|
||||
null, value.institutionId, value.configurationId,
|
||||
value.attributeId, value.listIndex, "2");
|
||||
|
@ -1188,8 +1202,9 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
|||
assertNotNull(valuesResponse);
|
||||
assertFalse(valuesResponse.hasError());
|
||||
values = valuesResponse.get();
|
||||
final ConfigurationValue _value = value;
|
||||
final ConfigurationValue currentValue =
|
||||
values.stream().filter(v -> v.attributeId == value.attributeId).findFirst().orElse(null);
|
||||
values.stream().filter(v -> v.attributeId == _value.attributeId).findFirst().orElse(null);
|
||||
assertNotNull(currentValue);
|
||||
assertEquals("2", currentValue.value);
|
||||
}
|
||||
|
@ -1336,12 +1351,10 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
|||
new GetFollowupConfiguration());
|
||||
|
||||
// get all configuration attributes
|
||||
final Collection<ConfigurationAttribute> attributes = restService
|
||||
final Collection<ConfigurationAttribute> attributes = new ArrayList<>(restService
|
||||
.getBuilder(GetConfigAttributes.class)
|
||||
.call()
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.collect(Collectors.toList());
|
||||
.getOrThrow());
|
||||
|
||||
// get configuration page
|
||||
final Result<Page<ConfigurationNode>> pageResponse = restService
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.util.Map;
|
|||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import org.apache.tomcat.util.buf.StringUtils;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
|
@ -24,6 +25,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestServiceImpl;
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigAttributes;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigurationTableValues;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.init.XMLAttributeLoader;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public abstract class UsecaseTestUtils {
|
||||
|
||||
|
@ -52,7 +54,7 @@ public abstract class UsecaseTestUtils {
|
|||
.stream()
|
||||
.collect(Collectors.toMap(attr -> attr.name, Function.identity()));
|
||||
|
||||
final XMLAttributeLoader xmlAttributeLoader = new XMLAttributeLoader();
|
||||
final XMLAttributeLoader xmlAttributeLoader = new XMLAttributeLoader(Mockito.mock(Cryptor.class));
|
||||
final String configuraedNames = StringUtils.join(xmlAttributeLoader.loadFromXML(
|
||||
1L,
|
||||
Long.parseLong(configId),
|
||||
|
@ -111,7 +113,7 @@ public abstract class UsecaseTestUtils {
|
|||
.stream()
|
||||
.collect(Collectors.toMap(attr -> attr.name, Function.identity()));
|
||||
|
||||
final XMLAttributeLoader xmlAttributeLoader = new XMLAttributeLoader();
|
||||
final XMLAttributeLoader xmlAttributeLoader = new XMLAttributeLoader(Mockito.mock(Cryptor.class));
|
||||
final String configuraedNames = StringUtils.join(xmlAttributeLoader.loadFromXML(
|
||||
1L,
|
||||
Long.parseLong(configId),
|
||||
|
|
|
@ -12,6 +12,7 @@ import static org.junit.Assert.assertEquals;
|
|||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
|
@ -31,23 +32,25 @@ public class ClientCredentialServiceTest {
|
|||
@Test
|
||||
public void testEncryptDecryptClientCredentials() {
|
||||
final Environment envMock = mock(Environment.class);
|
||||
when(envMock.getRequiredProperty(ClientCredentialServiceImpl.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY))
|
||||
when(envMock.getRequiredProperty(Cryptor.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY))
|
||||
.thenReturn("secret1");
|
||||
|
||||
Cryptor cryptor = new Cryptor(envMock);
|
||||
|
||||
final String clientName = "simpleClientName";
|
||||
|
||||
final ClientCredentialServiceImpl service = new ClientCredentialServiceImpl(envMock);
|
||||
final ClientCredentialServiceImpl service = new ClientCredentialServiceImpl(envMock, cryptor);
|
||||
String encrypted =
|
||||
service.encrypt(clientName, "secret1").toString();
|
||||
String decrypted = service.decrypt(encrypted, "secret1").toString();
|
||||
cryptor.encrypt(clientName, "secret1").toString();
|
||||
String decrypted = cryptor.decrypt(encrypted, "secret1").toString();
|
||||
|
||||
assertEquals(clientName, decrypted);
|
||||
|
||||
final String clientSecret = "fbjreij39ru29305ruࣣàèLöäöäü65%(/%(ç87";
|
||||
|
||||
encrypted =
|
||||
service.encrypt(clientSecret, "secret1").toString();
|
||||
decrypted = service.decrypt(encrypted, "secret1").toString();
|
||||
cryptor.encrypt(clientSecret, "secret1").toString();
|
||||
decrypted = cryptor.decrypt(encrypted, "secret1").toString();
|
||||
|
||||
assertEquals(clientSecret, decrypted);
|
||||
}
|
||||
|
|
|
@ -15,11 +15,13 @@ import java.util.List;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import org.junit.Test;
|
||||
|
||||
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.ConfigurationValue;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class ExamConfigImportHandlerTest {
|
||||
|
||||
|
@ -41,6 +43,7 @@ public class ExamConfigImportHandlerTest {
|
|||
public void simpleStringValueTest() throws Exception {
|
||||
final ValueCollector valueCollector = new ValueCollector();
|
||||
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
|
||||
Mockito.mock(Cryptor.class),
|
||||
1L,
|
||||
1L,
|
||||
valueCollector,
|
||||
|
@ -73,6 +76,7 @@ public class ExamConfigImportHandlerTest {
|
|||
public void simpleIntegerValueTest() throws Exception {
|
||||
final ValueCollector valueCollector = new ValueCollector();
|
||||
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
|
||||
Mockito.mock(Cryptor.class),
|
||||
1L,
|
||||
1L,
|
||||
valueCollector,
|
||||
|
@ -105,6 +109,7 @@ public class ExamConfigImportHandlerTest {
|
|||
public void simpleBooleanValueTest() throws Exception {
|
||||
final ValueCollector valueCollector = new ValueCollector();
|
||||
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
|
||||
Mockito.mock(Cryptor.class),
|
||||
1L,
|
||||
1L,
|
||||
valueCollector,
|
||||
|
@ -136,6 +141,7 @@ public class ExamConfigImportHandlerTest {
|
|||
public void arrayOfStringValueTest() throws Exception {
|
||||
final ValueCollector valueCollector = new ValueCollector();
|
||||
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
|
||||
Mockito.mock(Cryptor.class),
|
||||
1L,
|
||||
1L,
|
||||
valueCollector,
|
||||
|
@ -187,6 +193,7 @@ public class ExamConfigImportHandlerTest {
|
|||
return attributeResolver.apply(attrName);
|
||||
};
|
||||
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
|
||||
Mockito.mock(Cryptor.class),
|
||||
1L,
|
||||
1L,
|
||||
valueCollector,
|
||||
|
@ -256,6 +263,7 @@ public class ExamConfigImportHandlerTest {
|
|||
return attributeResolver.apply(attrName);
|
||||
};
|
||||
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
|
||||
Mockito.mock(Cryptor.class),
|
||||
1L,
|
||||
1L,
|
||||
valueCollector,
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.util.Collection;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
|
@ -320,8 +321,9 @@ public class TableConverterTest {
|
|||
}
|
||||
|
||||
private AttributeValueConverterService createAttributeValueConverterService() {
|
||||
final ClientCredentialService clientCredentialServiceMock = Mockito.mock(ClientCredentialService.class);
|
||||
final List<AttributeValueConverter> converter = new ArrayList<>();
|
||||
converter.add(new StringConverter());
|
||||
converter.add(new StringConverter(clientCredentialServiceMock));
|
||||
return new AttributeValueConverterServiceImpl(converter);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue