fallback attributes and new password attribute handling

This commit is contained in:
anhefti 2020-02-19 17:10:21 +01:00
parent 26561288c9
commit 7238369550
52 changed files with 11407 additions and 10482 deletions

View file

@ -120,6 +120,8 @@ public final class Constants {
public static final int GZIP_ID2 = 0x8B; public static final int GZIP_ID2 = 0x8B;
public static final int GZIP_CM = 8; 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 WHITE_RGB = new RGB(255, 255, 255);
public static final RGB BLACK_RGB = new RGB(0, 0, 0); public static final RGB BLACK_RGB = new RGB(0, 0, 0);

View file

@ -1,178 +1,187 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gbl.api; package ch.ethz.seb.sebserver.gbl.api;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
public class POSTMapper { public class POSTMapper {
public static final POSTMapper EMPTY_MAP = new POSTMapper(null); public static final POSTMapper EMPTY_MAP = new POSTMapper(null);
protected final MultiValueMap<String, String> params; protected final MultiValueMap<String, String> params;
public POSTMapper(final MultiValueMap<String, String> params) { public POSTMapper(final MultiValueMap<String, String> params) {
super(); super();
this.params = params != null this.params = params != null
? new LinkedMultiValueMap<>(params) ? new LinkedMultiValueMap<>(params)
: new LinkedMultiValueMap<>(); : new LinkedMultiValueMap<>();
} }
public String getString(final String name) { public String getString(final String name) {
return Utils.decodeFormURL_UTF_8(this.params.getFirst(name)); return Utils.decodeFormURL_UTF_8(this.params.getFirst(name));
} }
public char[] getCharArray(final String name) { public char[] getCharArray(final String name) {
final String value = getString(name); final String value = getString(name);
if (value == null || value.length() <= 0) { if (value == null || value.length() <= 0) {
return new char[] {}; return new char[] {};
} }
return value.toCharArray(); return value.toCharArray();
} }
public CharSequence getCharSequence(final String name) { public CharSequence getCharSequence(final String name) {
return CharBuffer.wrap(getCharArray(name)); return CharBuffer.wrap(getCharArray(name));
} }
public Long getLong(final String name) { public Long getLong(final String name) {
final String value = this.params.getFirst(name); final String value = this.params.getFirst(name);
if (StringUtils.isBlank(value)) { if (StringUtils.isBlank(value)) {
return null; return null;
} }
return Long.parseLong(value); return Long.parseLong(value);
} }
public Integer getInteger(final String name) { public Short getShort(final String name) {
final String value = this.params.getFirst(name); final String value = this.params.getFirst(name);
if (value == null) { if (StringUtils.isBlank(value)) {
return null; return null;
} }
return Integer.parseInt(value); return Short.parseShort(value);
} }
public Locale getLocale(final String name) { public Integer getInteger(final String name) {
final String value = this.params.getFirst(name); final String value = this.params.getFirst(name);
if (value == null) { if (value == null) {
return null; return null;
} }
return Locale.forLanguageTag(value); return Integer.parseInt(value);
} }
public boolean getBoolean(final String name) { public Locale getLocale(final String name) {
return BooleanUtils.toBoolean(this.params.getFirst(name)); final String value = this.params.getFirst(name);
} if (value == null) {
return null;
public Boolean getBooleanObject(final String name) { }
return BooleanUtils.toBooleanObject(this.params.getFirst(name));
} return Locale.forLanguageTag(value);
}
public Integer getBooleanAsInteger(final String name) {
final Boolean booleanObject = getBooleanObject(name); public boolean getBoolean(final String name) {
if (booleanObject == null) { return BooleanUtils.toBoolean(this.params.getFirst(name));
return null; }
}
return BooleanUtils.toIntegerObject(booleanObject); public Boolean getBooleanObject(final String name) {
} return BooleanUtils.toBooleanObject(this.params.getFirst(name));
}
public DateTimeZone getDateTimeZone(final String name) {
final String value = this.params.getFirst(name); public Integer getBooleanAsInteger(final String name) {
if (value == null) { final Boolean booleanObject = getBooleanObject(name);
return null; if (booleanObject == null) {
} return null;
try { }
return DateTimeZone.forID(value); return BooleanUtils.toIntegerObject(booleanObject);
} catch (final Exception e) { }
return null;
} public DateTimeZone getDateTimeZone(final String name) {
} final String value = this.params.getFirst(name);
if (value == null) {
public Set<String> getStringSet(final String name) { return null;
final List<String> list = this.params.get(name); }
if (list == null) { try {
return Collections.emptySet(); return DateTimeZone.forID(value);
} } catch (final Exception e) {
return Utils.immutableSetOf(list); return null;
} }
}
public <T extends Enum<T>> T getEnum(final String name, final Class<T> type, final T defaultValue) {
final T result = getEnum(name, type); public Set<String> getStringSet(final String name) {
if (result == null) { final List<String> list = this.params.get(name);
return defaultValue; if (list == null) {
} return Collections.emptySet();
}
return result; return Utils.immutableSetOf(list);
} }
public <T extends Enum<T>> T getEnum(final String name, final Class<T> type) { public <T extends Enum<T>> T getEnum(final String name, final Class<T> type, final T defaultValue) {
final String value = this.params.getFirst(name); final T result = getEnum(name, type);
if (value == null) { if (result == null) {
return null; return defaultValue;
} }
try {
return Enum.valueOf(type, value); return result;
} catch (final Exception e) { }
return null;
} public <T extends Enum<T>> T getEnum(final String name, final Class<T> type) {
} final String value = this.params.getFirst(name);
if (value == null) {
public DateTime getDateTime(final String name) { return null;
final String value = this.params.getFirst(name); }
if (value == null) { try {
return null; return Enum.valueOf(type, value);
} } catch (final Exception e) {
return null;
return Utils.toDateTime(value); }
} }
public List<Threshold> getThresholds() { public DateTime getDateTime(final String name) {
final List<String> thresholdStrings = this.params.get(Domain.THRESHOLD.REFERENCE_NAME); final String value = this.params.getFirst(name);
if (thresholdStrings == null || thresholdStrings.isEmpty()) { if (value == null) {
return Collections.emptyList(); return null;
} }
return thresholdStrings.stream() return Utils.toDateTime(value);
.map(ts -> { }
try {
final String[] split = StringUtils.split(ts, Constants.EMBEDDED_LIST_SEPARATOR); public List<Threshold> getThresholds() {
return new Threshold(Double.parseDouble(split[0]), split[1]); final List<String> thresholdStrings = this.params.get(Domain.THRESHOLD.REFERENCE_NAME);
} catch (final Exception e) { if (thresholdStrings == null || thresholdStrings.isEmpty()) {
return null; return Collections.emptyList();
} }
})
.collect(Collectors.toList()); return thresholdStrings.stream()
} .map(ts -> {
try {
@SuppressWarnings("unchecked") final String[] split = StringUtils.split(ts, Constants.EMBEDDED_LIST_SEPARATOR);
public <T extends POSTMapper> T putIfAbsent(final String name, final String value) { return new Threshold(Double.parseDouble(split[0]), split[1]);
this.params.putIfAbsent(name, Arrays.asList(value)); } catch (final Exception e) {
return (T) this; return null;
} }
})
} .collect(Collectors.toList());
}
@SuppressWarnings("unchecked")
public <T extends POSTMapper> T putIfAbsent(final String name, final String value) {
this.params.putIfAbsent(name, Arrays.asList(value));
return (T) this;
}
}

View file

@ -1,200 +1,337 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gbl.model.sebconfig; package ch.ethz.seb.sebserver.gbl.model.sebconfig;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.URL; import org.hibernate.validator.constraints.URL;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Activatable; import ch.ethz.seb.sebserver.gbl.model.Activatable;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Domain.SEB_CLIENT_CONFIGURATION; import ch.ethz.seb.sebserver.gbl.model.Domain.SEB_CLIENT_CONFIGURATION;
import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.GrantEntity; import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
public final class SebClientConfig implements GrantEntity, Activatable { public final class SebClientConfig implements GrantEntity, Activatable {
public static final String ATTR_FALLBACK_START_URL = "fallback_start_url"; public static final String ATTR_CONFIG_PURPOSE = "sebConfigPurpose";
public static final String ATTR_CONFIRM_ENCRYPT_SECRET = "confirm_encrypt_secret"; public static final String ATTR_FALLBACK = "sebServerFallback ";
public static final String ATTR_FALLBACK_START_URL = "startURL";
public static final String FILTER_ATTR_CREATION_DATE = "creation_date"; public static final String ATTR_FALLBACK_TIMEOUT = "sebServerFallbackTimeout";
public static final String ATTR_FALLBACK_ATTEMPTS = "sebServerFallbackAttempts";
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ID) public static final String ATTR_FALLBACK_ATTEMPT_INTERVAL = "sebServerFallbackAttemptInterval";
public final Long id; public static final String ATTR_FALLBACK_PASSWORD = "sebServerFallbackPasswordHash";
public static final String ATTR_FALLBACK_PASSWORD_CONFIRM = "sebServerFallbackPasswordHashConfirm";
@NotNull public static final String ATTR_QUIT_PASSWORD = "hashedQuitPassword";
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID) public static final String ATTR_QUIT_PASSWORD_CONFIRM = "hashedQuitPasswordConfirm";
public final Long institutionId; public static final String ATTR_ENCRYPT_SECRET_CONFIRM = "confirm_encrypt_secret";
@NotNull(message = "clientconfig:name:notNull") public static final String FILTER_ATTR_CREATION_DATE = "creation_date";
@Size(min = 3, max = 255, message = "clientconfig:name:size:{min}:{max}:${validatedValue}")
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_NAME) public enum ConfigPurpose {
public final String name; START_EXAM,
CONFIGURE_CLIENT
@JsonProperty(ATTR_FALLBACK_START_URL) }
@URL(message = "clientconfig:fallback_start_url:invalidURL")
public final String fallbackStartURL; @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ID)
public final Long id;
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_DATE)
public final DateTime date; @NotNull
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID)
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET) public final Long institutionId;
public final CharSequence encryptSecret;
@NotNull(message = "clientconfig:name:notNull")
@JsonProperty(ATTR_CONFIRM_ENCRYPT_SECRET) @Size(min = 3, max = 255, message = "clientconfig:name:size:{min}:{max}:${validatedValue}")
public final CharSequence confirmEncryptSecret; @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_NAME)
public final String name;
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE)
public final Boolean active; @NotNull(message = "clientconfig:sebConfigPurpose:notNull")
@JsonProperty(ATTR_CONFIG_PURPOSE)
@JsonCreator public final ConfigPurpose configPurpose;
public SebClientConfig(
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ID) final Long id, @JsonProperty(ATTR_FALLBACK)
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID) final Long institutionId, public final Boolean fallback;
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_NAME) final String name,
@JsonProperty(ATTR_FALLBACK_START_URL) final String fallbackStartURL, @JsonProperty(ATTR_FALLBACK_START_URL)
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_DATE) final DateTime date, @URL(message = "clientconfig:startURL:invalidURL")
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET) final CharSequence encryptSecret, public final String fallbackStartURL;
@JsonProperty(ATTR_CONFIRM_ENCRYPT_SECRET) final CharSequence confirmEncryptSecret,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) final Boolean active) { @JsonProperty(ATTR_FALLBACK_TIMEOUT)
public final Long fallbackTimeout;
this.id = id;
this.institutionId = institutionId; @JsonProperty(ATTR_FALLBACK_ATTEMPTS)
this.name = name; public final Short fallbackAttempts;
this.fallbackStartURL = fallbackStartURL;
this.date = date; @JsonProperty(ATTR_FALLBACK_ATTEMPT_INTERVAL)
this.encryptSecret = encryptSecret; public final Short fallbackAttemptInterval;
this.confirmEncryptSecret = confirmEncryptSecret;
this.active = active; @JsonProperty(ATTR_FALLBACK_PASSWORD)
} public final CharSequence fallbackPassword;
public SebClientConfig(final Long institutionId, final POSTMapper postParams) { @JsonProperty(ATTR_FALLBACK_PASSWORD_CONFIRM)
this.id = null; public final CharSequence fallbackPasswordConfirm;
this.institutionId = institutionId;
this.name = postParams.getString(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME); @JsonProperty(ATTR_QUIT_PASSWORD)
this.fallbackStartURL = postParams.getString(ATTR_FALLBACK_START_URL); public final CharSequence quitPassword;
this.date = postParams.getDateTime(Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE);
this.encryptSecret = postParams.getCharSequence(Domain.SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET); @JsonProperty(ATTR_QUIT_PASSWORD_CONFIRM)
this.confirmEncryptSecret = postParams.getCharSequence(ATTR_CONFIRM_ENCRYPT_SECRET); public final CharSequence quitPasswordConfirm;
this.active = false;
} @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_DATE)
public final DateTime date;
@Override
public EntityType entityType() { @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET)
return EntityType.SEB_CLIENT_CONFIGURATION; public final CharSequence encryptSecret;
}
@JsonProperty(ATTR_ENCRYPT_SECRET_CONFIRM)
@Override public final CharSequence encryptSecretConfirm;
public String getName() {
return this.name; @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE)
} public final Boolean active;
public String getFallbackStartURL() { @JsonCreator
return this.fallbackStartURL; public SebClientConfig(
} @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ID) final Long id,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID) final Long institutionId,
@Override @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_NAME) final String name,
public String getModelId() { @JsonProperty(ATTR_CONFIG_PURPOSE) final ConfigPurpose configPurpose,
return (this.id != null) @JsonProperty(ATTR_FALLBACK) final Boolean fallback,
? String.valueOf(this.id) @JsonProperty(ATTR_FALLBACK_START_URL) final String fallbackStartURL,
: null; @JsonProperty(ATTR_FALLBACK_TIMEOUT) final Long fallbackTimeout,
} @JsonProperty(ATTR_FALLBACK_ATTEMPTS) final Short fallbackAttempts,
@JsonProperty(ATTR_FALLBACK_ATTEMPT_INTERVAL) final Short fallbackAttemptInterval,
@Override @JsonProperty(ATTR_FALLBACK_PASSWORD) final CharSequence fallbackPassword,
public boolean isActive() { @JsonProperty(ATTR_FALLBACK_PASSWORD_CONFIRM) final CharSequence fallbackPasswordConfirm,
return this.active; @JsonProperty(ATTR_QUIT_PASSWORD) final CharSequence quitPassword,
} @JsonProperty(ATTR_QUIT_PASSWORD_CONFIRM) final CharSequence quitPasswordConfirm,
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_DATE) final DateTime date,
@Override @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET) final CharSequence encryptSecret,
public Long getInstitutionId() { @JsonProperty(ATTR_ENCRYPT_SECRET_CONFIRM) final CharSequence encryptSecretConfirm,
return this.institutionId; @JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) final Boolean active) {
}
this.id = id;
public Long getId() { this.institutionId = institutionId;
return this.id; this.name = name;
} this.configPurpose = configPurpose;
this.fallback = fallback;
public DateTime getDate() { this.fallbackStartURL = fallbackStartURL;
return this.date; this.fallbackTimeout = fallbackTimeout;
} this.fallbackAttempts = fallbackAttempts;
this.fallbackAttemptInterval = fallbackAttemptInterval;
@JsonIgnore this.fallbackPassword = fallbackPassword;
public CharSequence getEncryptSecret() { this.fallbackPasswordConfirm = fallbackPasswordConfirm;
return this.encryptSecret; this.quitPassword = quitPassword;
} this.quitPasswordConfirm = quitPasswordConfirm;
this.date = date;
@JsonIgnore this.encryptSecret = encryptSecret;
public CharSequence getConfirmEncryptSecret() { this.encryptSecretConfirm = encryptSecretConfirm;
return this.confirmEncryptSecret; this.active = active;
} }
@JsonIgnore public SebClientConfig(final Long institutionId, final POSTMapper postParams) {
public boolean hasEncryptionSecret() { this.id = null;
return this.encryptSecret != null && this.encryptSecret.length() > 0; this.institutionId = institutionId;
} this.name = postParams.getString(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME);
this.configPurpose = postParams.getEnum(ATTR_CONFIG_PURPOSE, ConfigPurpose.class);
public Boolean getActive() { this.fallback = postParams.getBoolean(ATTR_FALLBACK);
return this.active; this.fallbackStartURL = postParams.getString(ATTR_FALLBACK_START_URL);
} this.fallbackTimeout = postParams.getLong(ATTR_FALLBACK_TIMEOUT);
this.fallbackAttempts = postParams.getShort(ATTR_FALLBACK_ATTEMPTS);
@Override this.fallbackAttemptInterval = postParams.getShort(ATTR_FALLBACK_ATTEMPT_INTERVAL);
public Entity printSecureCopy() { this.fallbackPassword = postParams.getCharSequence(ATTR_FALLBACK_PASSWORD);
return new SebClientConfig( this.fallbackPasswordConfirm = postParams.getCharSequence(ATTR_FALLBACK_PASSWORD_CONFIRM);
this.id, this.quitPassword = postParams.getCharSequence(ATTR_QUIT_PASSWORD);
this.institutionId, this.quitPasswordConfirm = postParams.getCharSequence(ATTR_QUIT_PASSWORD_CONFIRM);
this.name, this.date = postParams.getDateTime(Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE);
this.fallbackStartURL, this.encryptSecret = postParams.getCharSequence(Domain.SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET);
this.date, this.encryptSecretConfirm = postParams.getCharSequence(ATTR_ENCRYPT_SECRET_CONFIRM);
Constants.EMPTY_NOTE, this.active = false;
Constants.EMPTY_NOTE, }
this.active);
} @Override
public EntityType entityType() {
@Override return EntityType.SEB_CLIENT_CONFIGURATION;
public String toString() { }
final StringBuilder builder = new StringBuilder();
builder.append("SebClientConfig [id="); @Override
builder.append(this.id); public String getName() {
builder.append(", institutionId="); return this.name;
builder.append(this.institutionId); }
builder.append(", name=");
builder.append(this.name); public String getFallbackStartURL() {
builder.append(", fallbackStartURL="); return this.fallbackStartURL;
builder.append(this.fallbackStartURL); }
builder.append(", date=");
builder.append(this.date); @Override
builder.append(", active="); public String getModelId() {
builder.append(this.active); return (this.id != null)
builder.append("]"); ? String.valueOf(this.id)
return builder.toString(); : null;
} }
public static final SebClientConfig createNew(final Long institutionId) { @Override
return new SebClientConfig( public boolean isActive() {
null, return this.active;
institutionId, }
null,
null, @Override
DateTime.now(DateTimeZone.UTC), public Long getInstitutionId() {
null, return this.institutionId;
null, }
false);
} public Long getId() {
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;
}
public CharSequence getEncryptSecret() {
return this.encryptSecret;
}
@JsonIgnore
public CharSequence getEncryptSecretConfirm() {
return this.encryptSecretConfirm;
}
@JsonIgnore
public boolean hasEncryptionSecret() {
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);
}
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,
null,
false);
}
}

View 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;
}
}
}

View file

@ -14,6 +14,8 @@ import java.net.URLEncoder;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -28,6 +30,7 @@ import java.util.function.Predicate;
import java.util.stream.Collector; import java.util.stream.Collector;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils; import org.apache.commons.text.StringEscapeUtils;
import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.RGB;
@ -478,6 +481,20 @@ public final class Utils {
return (text == null) ? null : Constants.PERCENTAGE + text + Constants.PERCENTAGE; 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") @SuppressWarnings("unchecked")
public static <T> Predicate<T> truePredicate() { public static <T> Predicate<T> truePredicate() {
return (Predicate<T>) TRUE_PREDICATE; return (Predicate<T>) TRUE_PREDICATE;

View file

@ -213,7 +213,7 @@ final class ExamToConfigBindingForm {
String.valueOf(examConfigurationMap.configurationNodeId), String.valueOf(examConfigurationMap.configurationNodeId),
resourceService::examConfigurationSelectionResources) resourceService::examConfigurationSelectionResources)
.withSelectionListener(form -> updateFormValuesFromConfigSelection(form, resourceService)) .withSelectionListener(form -> updateFormValuesFromConfigSelection(form, resourceService))
.mandatory()) .mandatory())
.addField(FormBuilder.text( .addField(FormBuilder.text(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
@ -228,14 +228,15 @@ final class ExamToConfigBindingForm {
resourceService.localizedExamConfigStatusName(examConfigurationMap)) resourceService.localizedExamConfigStatusName(examConfigurationMap))
.readonly(true)) .readonly(true))
.addField(FormBuilder.text( .addField(FormBuilder.password(
Domain.EXAM_CONFIGURATION_MAP.ATTR_ENCRYPT_SECRET, Domain.EXAM_CONFIGURATION_MAP.ATTR_ENCRYPT_SECRET,
FORM_ENCRYPT_SECRET_TEXT_KEY) FORM_ENCRYPT_SECRET_TEXT_KEY,
.asPasswordField()) examConfigurationMap.encryptSecret))
.addField(FormBuilder.text(
.addField(FormBuilder.password(
ExamConfigurationMap.ATTR_CONFIRM_ENCRYPT_SECRET, ExamConfigurationMap.ATTR_CONFIRM_ENCRYPT_SECRET,
FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY) FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY,
.asPasswordField()) examConfigurationMap.encryptSecret))
.build(); .build();

View file

@ -8,8 +8,14 @@
package ch.ethz.seb.sebserver.gui.content; 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.RWT;
import org.eclipse.rap.rwt.client.service.UrlLauncher; 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.eclipse.swt.widgets.Composite;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; 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.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Lazy @Lazy
@Component @Component
@GuiProfile @GuiProfile
@ -53,15 +63,51 @@ public class SebClientConfigForm implements TemplateComposer {
new LocTextKey("sebserver.clientconfig.form.title"); new LocTextKey("sebserver.clientconfig.form.title");
private static final LocTextKey FORM_NAME_TEXT_KEY = private static final LocTextKey FORM_NAME_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.name"); 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 = private static final LocTextKey FORM_DATE_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.date"); 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 = private static final LocTextKey FORM_ENCRYPT_SECRET_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.encryptSecret"); new LocTextKey("sebserver.clientconfig.form.encryptSecret");
private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY = private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY =
new LocTextKey("sebserver.clientconfig.form.encryptSecret.confirm"); 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 PageService pageService;
private final RestService restService; private final RestService restService;
private final CurrentUser currentUser; private final CurrentUser currentUser;
@ -131,32 +177,147 @@ public class SebClientConfigForm implements TemplateComposer {
.putStaticValue( .putStaticValue(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID, Domain.SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID,
String.valueOf(clientConfig.getInstitutionId())) 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, .addFieldIf(() -> !isNew,
() -> FormBuilder.text( () -> FormBuilder.text(
Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE, Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE,
FORM_DATE_TEXT_KEY, FORM_DATE_TEXT_KEY,
i18nSupport.formatDisplayDateWithTimeZone(clientConfig.date)) i18nSupport.formatDisplayDateWithTimeZone(clientConfig.date))
.readonly(true)) .readonly(true))
.addField(FormBuilder.text( .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, Domain.SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET,
FORM_ENCRYPT_SECRET_TEXT_KEY) FORM_ENCRYPT_SECRET_TEXT_KEY,
.asPasswordField()) 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( .addField(FormBuilder.text(
SebClientConfig.ATTR_CONFIRM_ENCRYPT_SECRET, SebClientConfig.ATTR_FALLBACK_START_URL,
FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY) FALLBACK_URL_TEXT_KEY,
.asPasswordField()) 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) .buildFor((isNew)
? this.restService.getRestCall(NewClientConfig.class) ? this.restService.getRestCall(NewClientConfig.class)
: this.restService.getRestCall(SaveClientConfig.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); final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
this.pageService.pageActionBuilder(formContext.clearEntityKeys()) this.pageService.pageActionBuilder(formContext.clearEntityKeys())
@ -208,4 +369,14 @@ public class SebClientConfigForm implements TemplateComposer {
.publishIf(() -> !isReadonly); .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");
}
}
} }

View file

@ -1,281 +1,281 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.content; package ch.ethz.seb.sebserver.gui.content;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Control;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.form.Form; import ch.ethz.seb.sebserver.gui.form.Form;
import ch.ethz.seb.sebserver.gui.form.FormBuilder; import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.form.FormHandle; import ch.ethz.seb.sebserver.gui.form.FormHandle;
import ch.ethz.seb.sebserver.gui.service.ResourceService; import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer; import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException; import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent; import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog; import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportExamConfigOnExistingConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportExamConfigOnExistingConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportNewExamConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ImportNewExamConfig;
import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection; import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection;
final class SebExamConfigImportPopup { final class SebExamConfigImportPopup {
private final static PageMessageException MISSING_PASSWORD = new PageMessageException( private final static PageMessageException MISSING_PASSWORD = new PageMessageException(
new LocTextKey("sebserver.examconfig.action.import.missing-password")); new LocTextKey("sebserver.examconfig.action.import.missing-password"));
static Function<PageAction, PageAction> importFunction( static Function<PageAction, PageAction> importFunction(
final PageService pageService, final PageService pageService,
final boolean newConfig) { final boolean newConfig) {
return action -> { return action -> {
final ModalInputDialog<FormHandle<ConfigurationNode>> dialog = final ModalInputDialog<FormHandle<ConfigurationNode>> dialog =
new ModalInputDialog<FormHandle<ConfigurationNode>>( new ModalInputDialog<FormHandle<ConfigurationNode>>(
action.pageContext().getParent().getShell(), action.pageContext().getParent().getShell(),
pageService.getWidgetFactory()) pageService.getWidgetFactory())
.setLargeDialogWidth(); .setLargeDialogWidth();
final ImportFormContext importFormContext = new ImportFormContext( final ImportFormContext importFormContext = new ImportFormContext(
pageService, pageService,
action.pageContext(), action.pageContext(),
newConfig); newConfig);
dialog.open( dialog.open(
SebExamConfigPropForm.FORM_IMPORT_TEXT_KEY, SebExamConfigPropForm.FORM_IMPORT_TEXT_KEY,
(Predicate<FormHandle<ConfigurationNode>>) formHandle -> doImport( (Predicate<FormHandle<ConfigurationNode>>) formHandle -> doImport(
pageService, pageService,
formHandle, formHandle,
newConfig), newConfig),
importFormContext::cancelUpload, importFormContext::cancelUpload,
importFormContext); importFormContext);
return action; return action;
}; };
} }
private static final boolean doImport( private static final boolean doImport(
final PageService pageService, final PageService pageService,
final FormHandle<ConfigurationNode> formHandle, final FormHandle<ConfigurationNode> formHandle,
final boolean newConfig) { final boolean newConfig) {
try { try {
final Form form = formHandle.getForm(); final Form form = formHandle.getForm();
final EntityKey entityKey = formHandle.getContext().getEntityKey(); 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(); final PageContext context = formHandle.getContext();
// Ad-hoc field validation // Ad-hoc field validation
formHandle.process(name -> true, field -> field.resetError()); formHandle.process(name -> true, field -> field.resetError());
final String fieldValue = form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME); final String fieldValue = form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME);
if (StringUtils.isBlank(fieldValue)) { if (StringUtils.isBlank(fieldValue)) {
form.setFieldError( form.setFieldError(
Domain.CONFIGURATION_NODE.ATTR_NAME, Domain.CONFIGURATION_NODE.ATTR_NAME,
pageService pageService
.getI18nSupport() .getI18nSupport()
.getText(new LocTextKey("sebserver.form.validation.fieldError.notNull"))); .getText(new LocTextKey("sebserver.form.validation.fieldError.notNull")));
return false; return false;
} else if (fieldValue.length() < 3 || fieldValue.length() > 255) { } else if (fieldValue.length() < 3 || fieldValue.length() > 255) {
form.setFieldError( form.setFieldError(
Domain.CONFIGURATION_NODE.ATTR_NAME, Domain.CONFIGURATION_NODE.ATTR_NAME,
pageService pageService
.getI18nSupport() .getI18nSupport()
.getText(new LocTextKey("sebserver.form.validation.fieldError.size", .getText(new LocTextKey("sebserver.form.validation.fieldError.size",
null, null,
null, null,
null, null,
3, 3,
255))); 255)));
return false; return false;
} }
if (fieldControl != null && fieldControl instanceof FileUploadSelection) { if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
final FileUploadSelection fileUpload = (FileUploadSelection) fieldControl; final FileUploadSelection fileUpload = (FileUploadSelection) fieldControl;
final InputStream inputStream = fileUpload.getInputStream(); final InputStream inputStream = fileUpload.getInputStream();
if (inputStream != null) { if (inputStream != null) {
final RestCall<Configuration>.RestCallBuilder restCall = (newConfig) final RestCall<Configuration>.RestCallBuilder restCall = (newConfig)
? pageService.getRestService() ? pageService.getRestService()
.getBuilder(ImportNewExamConfig.class) .getBuilder(ImportNewExamConfig.class)
: pageService.getRestService() : pageService.getRestService()
.getBuilder(ImportExamConfigOnExistingConfig.class); .getBuilder(ImportExamConfigOnExistingConfig.class);
restCall restCall
.withHeader( .withHeader(
API.IMPORT_PASSWORD_ATTR_NAME, API.IMPORT_PASSWORD_ATTR_NAME,
form.getFieldValue(API.IMPORT_PASSWORD_ATTR_NAME)) form.getFieldValue(API.IMPORT_PASSWORD_ATTR_NAME))
.withBody(inputStream); .withBody(inputStream);
if (newConfig) { if (newConfig) {
restCall restCall
.withHeader( .withHeader(
Domain.CONFIGURATION_NODE.ATTR_NAME, Domain.CONFIGURATION_NODE.ATTR_NAME,
form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME)) form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_NAME))
.withHeader( .withHeader(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION)) form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION))
.withHeader( .withHeader(
Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID, Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID,
form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID)); form.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID));
} else { } else {
restCall.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId); restCall.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId);
} }
final Result<Configuration> configuration = restCall final Result<Configuration> configuration = restCall
.call(); .call();
if (!configuration.hasError()) { if (!configuration.hasError()) {
context.publishInfo(SebExamConfigPropForm.FORM_IMPORT_CONFIRM_TEXT_KEY); context.publishInfo(SebExamConfigPropForm.FORM_IMPORT_CONFIRM_TEXT_KEY);
if (newConfig) { if (newConfig) {
final PageAction action = pageService.pageActionBuilder(context) final PageAction action = pageService.pageActionBuilder(context)
.newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_TO_NEW_CONFIG) .newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_TO_NEW_CONFIG)
.create(); .create();
pageService.firePageEvent( pageService.firePageEvent(
new ActionEvent(action), new ActionEvent(action),
action.pageContext()); action.pageContext());
} }
return true; return true;
} else { } else {
final Exception error = configuration.getError(); final Exception error = configuration.getError();
if (error instanceof RestCallError) { if (error instanceof RestCallError) {
((RestCallError) error) ((RestCallError) error)
.getErrorMessages() .getErrorMessages()
.stream() .stream()
.findFirst() .findFirst()
.ifPresent(message -> { .ifPresent(message -> {
if (APIMessage.ErrorMessage.MISSING_PASSWORD.isOf(message)) { if (APIMessage.ErrorMessage.MISSING_PASSWORD.isOf(message)) {
formHandle formHandle
.getContext() .getContext()
.publishPageMessage(MISSING_PASSWORD); .publishPageMessage(MISSING_PASSWORD);
} else { } else {
formHandle formHandle
.getContext() .getContext()
.notifyImportError(EntityType.CONFIGURATION_NODE, error); .notifyImportError(EntityType.CONFIGURATION_NODE, error);
} }
}); });
return true; return true;
} }
formHandle.getContext().notifyError( formHandle.getContext().notifyError(
SebExamConfigPropForm.FORM_TITLE, SebExamConfigPropForm.FORM_TITLE,
configuration.getError()); configuration.getError());
return true; return true;
} }
} else { } else {
formHandle.getContext().publishPageMessage( formHandle.getContext().publishPageMessage(
new LocTextKey("sebserver.error.unexpected"), new LocTextKey("sebserver.error.unexpected"),
new LocTextKey("Please selecte a valid SEB Exam Configuration File")); new LocTextKey("Please selecte a valid SEB Exam Configuration File"));
} }
} }
return false; return false;
} catch (final Exception e) { } catch (final Exception e) {
formHandle.getContext().notifyError(SebExamConfigPropForm.FORM_TITLE, e); formHandle.getContext().notifyError(SebExamConfigPropForm.FORM_TITLE, e);
return true; return true;
} }
} }
private static final class ImportFormContext implements ModalInputDialogComposer<FormHandle<ConfigurationNode>> { private static final class ImportFormContext implements ModalInputDialogComposer<FormHandle<ConfigurationNode>> {
private final PageService pageService; private final PageService pageService;
private final PageContext pageContext; private final PageContext pageContext;
private final boolean newConfig; private final boolean newConfig;
private Form form = null; private Form form = null;
protected ImportFormContext( protected ImportFormContext(
final PageService pageService, final PageService pageService,
final PageContext pageContext, final PageContext pageContext,
final boolean newConfig) { final boolean newConfig) {
this.pageService = pageService; this.pageService = pageService;
this.pageContext = pageContext; this.pageContext = pageContext;
this.newConfig = newConfig; this.newConfig = newConfig;
} }
@Override @Override
public Supplier<FormHandle<ConfigurationNode>> compose(final Composite parent) { public Supplier<FormHandle<ConfigurationNode>> compose(final Composite parent) {
final Composite grid = this.pageService.getWidgetFactory() final Composite grid = this.pageService.getWidgetFactory()
.createPopupScrollComposite(parent); .createPopupScrollComposite(parent);
final ResourceService resourceService = this.pageService.getResourceService(); final ResourceService resourceService = this.pageService.getResourceService();
final List<Tuple<String>> examConfigTemplateResources = resourceService.getExamConfigTemplateResources(); final List<Tuple<String>> examConfigTemplateResources = resourceService.getExamConfigTemplateResources();
final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder( final FormHandle<ConfigurationNode> formHandle = this.pageService.formBuilder(
this.pageContext.copyOf(grid)) this.pageContext.copyOf(grid))
.readonly(false) .readonly(false)
.addField(FormBuilder.fileUpload( .addField(FormBuilder.fileUpload(
API.IMPORT_FILE_ATTR_NAME, API.IMPORT_FILE_ATTR_NAME,
SebExamConfigPropForm.FORM_IMPORT_SELECT_TEXT_KEY, SebExamConfigPropForm.FORM_IMPORT_SELECT_TEXT_KEY,
null, null,
API.SEB_FILE_EXTENSION)) API.SEB_FILE_EXTENSION))
.addFieldIf( .addFieldIf(
() -> this.newConfig, () -> this.newConfig,
() -> FormBuilder.text( () -> FormBuilder.text(
Domain.CONFIGURATION_NODE.ATTR_NAME, Domain.CONFIGURATION_NODE.ATTR_NAME,
SebExamConfigPropForm.FORM_NAME_TEXT_KEY)) SebExamConfigPropForm.FORM_NAME_TEXT_KEY))
.addFieldIf( .addFieldIf(
() -> this.newConfig, () -> this.newConfig,
() -> FormBuilder.text( () -> FormBuilder.text(
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
SebExamConfigPropForm.FORM_DESCRIPTION_TEXT_KEY) SebExamConfigPropForm.FORM_DESCRIPTION_TEXT_KEY)
.asArea()) .asArea())
.addFieldIf( .addFieldIf(
() -> this.newConfig && !examConfigTemplateResources.isEmpty(), () -> this.newConfig && !examConfigTemplateResources.isEmpty(),
() -> FormBuilder.singleSelection( () -> FormBuilder.singleSelection(
Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID, Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID,
SebExamConfigPropForm.FORM_TEMPLATE_TEXT_KEY, SebExamConfigPropForm.FORM_TEMPLATE_TEXT_KEY,
null, null,
resourceService::getExamConfigTemplateResources)) resourceService::getExamConfigTemplateResources))
.addField(FormBuilder.text( .addField(FormBuilder.text(
API.IMPORT_PASSWORD_ATTR_NAME, API.IMPORT_PASSWORD_ATTR_NAME,
SebExamConfigPropForm.FORM_IMPORT_PASSWORD_TEXT_KEY, SebExamConfigPropForm.FORM_IMPORT_PASSWORD_TEXT_KEY,
"").asPasswordField()) "").asPasswordField())
.build(); .build();
this.form = formHandle.getForm(); this.form = formHandle.getForm();
return () -> formHandle; return () -> formHandle;
} }
void cancelUpload() { void cancelUpload() {
if (this.form != null) { 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) { if (fieldControl != null && fieldControl instanceof FileUploadSelection) {
((FileUploadSelection) fieldControl).close(); ((FileUploadSelection) fieldControl).close();
} }
} }
} }
} }
} }

View file

@ -1,47 +1,54 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.form; package ch.ethz.seb.sebserver.gui.form;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
public class CheckboxFieldBuilder extends FieldBuilder<String> {
import java.util.function.Consumer;
protected CheckboxFieldBuilder(final String name, final LocTextKey label, final String value) {
super(name, label, value); public class CheckboxFieldBuilder extends FieldBuilder<String> {
}
protected CheckboxFieldBuilder(
@Override final String name,
void build(final FormBuilder builder) { final LocTextKey label,
final boolean readonly = builder.readonly || this.readonly; final String value) {
final Label titleLabel = createTitleLabel(builder.formParent, builder, this);
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput); super(name, label, value);
final Button checkbox = builder.widgetFactory.buttonLocalized( }
fieldGrid,
SWT.CHECK, @Override
null, null); void build(final FormBuilder builder) {
final boolean readonly = builder.readonly || this.readonly;
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true); final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
checkbox.setLayoutData(gridData); final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
checkbox.setSelection(BooleanUtils.toBoolean(this.value)); final Button checkbox = builder.widgetFactory.buttonLocalized(
fieldGrid,
if (readonly) { SWT.CHECK,
checkbox.setEnabled(false); null, null);
}
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
builder.form.putField(this.name, titleLabel, checkbox); checkbox.setLayoutData(gridData);
} checkbox.setSelection(BooleanUtils.toBoolean(this.value));
} if (readonly) {
checkbox.setEnabled(false);
}
builder.form.putField(this.name, titleLabel, checkbox);
}
}

View file

@ -1,238 +1,239 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.form; package ch.ethz.seb.sebserver.gui.form;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
public abstract class FieldBuilder<T> {
public abstract class FieldBuilder<T> {
public static final LocTextKey MANDATORY_TEXT_KEY = new LocTextKey("sebserver.form.mandatory");
public static final String TOOLTIP_KEY_SUFFIX_LABEL = ".tooltip"; public static final LocTextKey MANDATORY_TEXT_KEY = new LocTextKey("sebserver.form.mandatory");
public static final String TOOLTIP_KEY_SUFFIX_LEFT = ".tooltip.left"; public static final String TOOLTIP_KEY_SUFFIX_LABEL = ".tooltip";
public static final String TOOLTIP_KEY_SUFFIX_RIGHT = ".tooltip.right"; public static final String TOOLTIP_KEY_SUFFIX_LEFT = ".tooltip.left";
public static final String TOOLTIP_KEY_SUFFIX_RIGHT = ".tooltip.right";
int spanLabel = -1;
int spanInput = -1; int spanLabel = -1;
int spanEmptyCell = -1; int spanInput = -1;
int titleValign = SWT.TOP; int spanEmptyCell = -1;
Boolean autoEmptyCellSeparation = null; int titleValign = SWT.TOP;
String group = null; Boolean autoEmptyCellSeparation = null;
boolean readonly = false; String group = null;
boolean visible = true; boolean readonly = false;
String defaultLabel = null; boolean visible = true;
boolean isMandatory = false; String defaultLabel = null;
boolean isMandatory = false;
final String name;
final LocTextKey label; final String name;
final LocTextKey tooltipLabel; final LocTextKey label;
final LocTextKey tooltipKeyLeft; final LocTextKey tooltipLabel;
final LocTextKey tooltipKeyRight; final LocTextKey tooltipKeyLeft;
final T value; final LocTextKey tooltipKeyRight;
final T value;
protected FieldBuilder(final String name, final LocTextKey label, final T value) {
this.name = name; protected FieldBuilder(final String name, final LocTextKey label, final T value) {
this.label = label; this.name = name;
this.value = value; this.label = label;
this.tooltipLabel = (label != null) ? new LocTextKey(label.name + TOOLTIP_KEY_SUFFIX_LABEL) : null; this.value = value;
this.tooltipKeyLeft = (label != null) ? new LocTextKey(label.name + TOOLTIP_KEY_SUFFIX_LEFT) : null; this.tooltipLabel = (label != null) ? new LocTextKey(label.name + TOOLTIP_KEY_SUFFIX_LABEL) : null;
this.tooltipKeyRight = (label != null) ? new LocTextKey(label.name + TOOLTIP_KEY_SUFFIX_RIGHT) : null; this.tooltipKeyLeft = (label != null) ? new LocTextKey(label.name + TOOLTIP_KEY_SUFFIX_LEFT) : null;
} this.tooltipKeyRight = (label != null) ? new LocTextKey(label.name + TOOLTIP_KEY_SUFFIX_RIGHT) : null;
}
public FieldBuilder<T> withDefaultLabel(final String defaultLabel) {
this.defaultLabel = defaultLabel; public FieldBuilder<T> withDefaultLabel(final String defaultLabel) {
return this; this.defaultLabel = defaultLabel;
} return this;
}
public FieldBuilder<T> withLabelSpan(final int span) {
this.spanLabel = span; public FieldBuilder<T> withLabelSpan(final int span) {
return this; this.spanLabel = span;
} return this;
}
public FieldBuilder<T> mandatory() {
this.isMandatory = true; public FieldBuilder<T> mandatory() {
return this; this.isMandatory = true;
} return this;
}
public FieldBuilder<T> mandatory(final boolean mandatory) {
this.isMandatory = mandatory; public FieldBuilder<T> mandatory(final boolean mandatory) {
return this; this.isMandatory = mandatory;
} return this;
}
public FieldBuilder<T> withInputSpan(final int span) {
this.spanInput = span; public FieldBuilder<T> withInputSpan(final int span) {
return this; this.spanInput = span;
} return this;
}
public FieldBuilder<T> withEmptyCellSpan(final int span) {
this.spanEmptyCell = span; public FieldBuilder<T> withEmptyCellSpan(final int span) {
return this; this.spanEmptyCell = span;
} return this;
}
public FieldBuilder<T> withEmptyCellSeparation(final boolean separation) {
this.autoEmptyCellSeparation = separation; public FieldBuilder<T> withEmptyCellSeparation(final boolean separation) {
return this; this.autoEmptyCellSeparation = separation;
} return this;
}
public FieldBuilder<T> withGroup(final String group) {
this.group = group; public FieldBuilder<T> withGroup(final String group) {
return this; this.group = group;
} return this;
}
public FieldBuilder<T> readonly(final boolean readonly) {
this.readonly = readonly; public FieldBuilder<T> readonly(final boolean readonly) {
return this; this.readonly = readonly;
} return this;
}
public FieldBuilder<T> visibleIf(final boolean visible) {
this.visible = visible; public FieldBuilder<T> visibleIf(final boolean visible) {
return this; this.visible = visible;
} return this;
}
public FieldBuilder<T> readonlyIf(final BooleanSupplier readonly) {
this.readonly = readonly != null && readonly.getAsBoolean(); public FieldBuilder<T> readonlyIf(final BooleanSupplier readonly) {
return this; this.readonly = readonly != null && readonly.getAsBoolean();
} return this;
}
abstract void build(FormBuilder builder);
abstract void build(FormBuilder builder);
protected static Label createTitleLabel(
final Composite parent, protected static Control createTitleLabel(
final FormBuilder builder, final Composite parent,
final FieldBuilder<?> fieldBuilder) { final FormBuilder builder,
final FieldBuilder<?> fieldBuilder) {
if (fieldBuilder.label == null) {
return null; if (fieldBuilder.label == null) {
} return null;
}
final Composite infoGrid = new Composite(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(4, false); final Composite infoGrid = new Composite(parent, SWT.NONE);
gridLayout.verticalSpacing = 0; final GridLayout gridLayout = new GridLayout(4, false);
gridLayout.marginHeight = 0; gridLayout.verticalSpacing = 0;
gridLayout.marginWidth = 0; gridLayout.marginHeight = 0;
gridLayout.marginRight = 0; gridLayout.marginWidth = 0;
infoGrid.setLayout(gridLayout); gridLayout.marginRight = 0;
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); infoGrid.setLayout(gridLayout);
gridData.horizontalSpan = (fieldBuilder.spanLabel > 0) ? fieldBuilder.spanLabel : 1; final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
infoGrid.setLayoutData(gridData); gridData.horizontalSpan = (fieldBuilder.spanLabel > 0) ? fieldBuilder.spanLabel : 1;
infoGrid.setLayoutData(gridData);
if (fieldBuilder.tooltipKeyLeft != null &&
StringUtils.isNotBlank(builder.i18nSupport.getText(fieldBuilder.tooltipKeyLeft, ""))) { if (fieldBuilder.tooltipKeyLeft != null &&
StringUtils.isNotBlank(builder.i18nSupport.getText(fieldBuilder.tooltipKeyLeft, ""))) {
final Label info = builder.widgetFactory.imageButton(
WidgetFactory.ImageIcon.HELP, final Label info = builder.widgetFactory.imageButton(
infoGrid, WidgetFactory.ImageIcon.HELP,
fieldBuilder.tooltipKeyLeft); infoGrid,
info.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); fieldBuilder.tooltipKeyLeft);
} info.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
}
final boolean hasLabelTooltip = (fieldBuilder.tooltipLabel != null &&
StringUtils.isNotBlank(builder.i18nSupport.getText(fieldBuilder.tooltipLabel, ""))); final boolean hasLabelTooltip = (fieldBuilder.tooltipLabel != null &&
StringUtils.isNotBlank(builder.i18nSupport.getText(fieldBuilder.tooltipLabel, "")));
final Label label = labelLocalized(
builder.widgetFactory, final Label label = labelLocalized(
infoGrid, builder.widgetFactory,
fieldBuilder.label, infoGrid,
fieldBuilder.defaultLabel, fieldBuilder.label,
(hasLabelTooltip) ? fieldBuilder.tooltipLabel : null, fieldBuilder.defaultLabel,
1, (hasLabelTooltip) ? fieldBuilder.tooltipLabel : null,
fieldBuilder.titleValign); 1,
fieldBuilder.titleValign);
if (fieldBuilder.isMandatory) {
final Label mandatory = builder.widgetFactory.imageButton( if (fieldBuilder.isMandatory) {
WidgetFactory.ImageIcon.MANDATORY, final Label mandatory = builder.widgetFactory.imageButton(
infoGrid, WidgetFactory.ImageIcon.MANDATORY,
MANDATORY_TEXT_KEY); infoGrid,
mandatory.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); MANDATORY_TEXT_KEY);
} mandatory.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
}
if (fieldBuilder.tooltipKeyRight != null &&
StringUtils.isNotBlank(builder.i18nSupport.getText(fieldBuilder.tooltipKeyRight, ""))) { if (fieldBuilder.tooltipKeyRight != null &&
StringUtils.isNotBlank(builder.i18nSupport.getText(fieldBuilder.tooltipKeyRight, ""))) {
final Label info = builder.widgetFactory.imageButton(
WidgetFactory.ImageIcon.HELP, final Label info = builder.widgetFactory.imageButton(
infoGrid, WidgetFactory.ImageIcon.HELP,
fieldBuilder.tooltipKeyRight); infoGrid,
info.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); fieldBuilder.tooltipKeyRight);
} info.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
}
return label;
} return infoGrid;
}
public static Label labelLocalized(
final WidgetFactory widgetFactory, public static Label labelLocalized(
final Composite parent, final WidgetFactory widgetFactory,
final LocTextKey locTextKey, final Composite parent,
final String defaultText, final LocTextKey locTextKey,
final int hspan) { final String defaultText,
final int hspan) {
return labelLocalized(widgetFactory, parent, locTextKey, defaultText, null, hspan, SWT.CENTER);
} return labelLocalized(widgetFactory, parent, locTextKey, defaultText, null, hspan, SWT.CENTER);
}
public static final Label labelLocalized(
final WidgetFactory widgetFactory, public static Label labelLocalized(
final Composite parent, final WidgetFactory widgetFactory,
final LocTextKey locTextKey, final Composite parent,
final String defaultText, final LocTextKey locTextKey,
final LocTextKey tooltipTextKey, final String defaultText,
final int hspan, final LocTextKey tooltipTextKey,
final int verticalAlignment) { final int hspan,
final int verticalAlignment) {
final LocTextKey labelKey = StringUtils.isNotBlank(defaultText)
? new LocTextKey(defaultText) final LocTextKey labelKey = StringUtils.isNotBlank(defaultText)
: locTextKey; ? new LocTextKey(defaultText)
: locTextKey;
final Label label = widgetFactory.labelLocalized(
parent, final Label label = widgetFactory.labelLocalized(
labelKey, parent,
tooltipTextKey); labelKey,
final GridData gridData = new GridData(SWT.LEFT, verticalAlignment, false, false, hspan, 1); tooltipTextKey);
gridData.heightHint = FormBuilder.FORM_ROW_HEIGHT; final GridData gridData = new GridData(SWT.LEFT, verticalAlignment, false, false, hspan, 1);
label.setLayoutData(gridData); gridData.heightHint = FormBuilder.FORM_ROW_HEIGHT;
label.setData(RWT.CUSTOM_VARIANT, CustomVariant.TITLE_LABEL.key); label.setLayoutData(gridData);
return label; label.setData(RWT.CUSTOM_VARIANT, CustomVariant.TITLE_LABEL.key);
} return label;
}
public static Composite createFieldGrid(final Composite parent, final int hspan) {
final Composite fieldGrid = new Composite(parent, SWT.NONE); public static Composite createFieldGrid(final Composite parent, final int hspan) {
final GridLayout gridLayout = new GridLayout(); final Composite fieldGrid = new Composite(parent, SWT.NONE);
gridLayout.verticalSpacing = 0; final GridLayout gridLayout = new GridLayout();
gridLayout.marginHeight = 0; gridLayout.verticalSpacing = 0;
gridLayout.marginWidth = 0; gridLayout.marginHeight = 0;
gridLayout.marginRight = 0; gridLayout.marginWidth = 0;
fieldGrid.setLayout(gridLayout); gridLayout.marginRight = 0;
fieldGrid.setLayout(gridLayout);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.horizontalSpan = hspan; final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
fieldGrid.setLayoutData(gridData); gridData.horizontalSpan = hspan;
fieldGrid.setLayoutData(gridData);
return fieldGrid;
} return fieldGrid;
}
public static Label createErrorLabel(final Composite innerGrid) {
final Label errorLabel = new Label(innerGrid, SWT.NONE); public static Label createErrorLabel(final Composite innerGrid) {
final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true); final Label errorLabel = new Label(innerGrid, SWT.NONE);
errorLabel.setLayoutData(gridData); final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true);
errorLabel.setVisible(false); errorLabel.setLayoutData(gridData);
errorLabel.setData(RWT.CUSTOM_VARIANT, CustomVariant.ERROR.key); errorLabel.setVisible(false);
return errorLabel; errorLabel.setData(RWT.CUSTOM_VARIANT, CustomVariant.ERROR.key);
} return errorLabel;
}
} }

View file

@ -1,52 +1,53 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.form; package ch.ethz.seb.sebserver.gui.form;
import java.util.Collection; import java.util.Collection;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.FileUploadSelection;
public class FileUploadFieldBuilder extends FieldBuilder<String> {
public class FileUploadFieldBuilder extends FieldBuilder<String> {
private final Collection<String> supportedFiles;
private final Collection<String> supportedFiles;
FileUploadFieldBuilder(
final String name, FileUploadFieldBuilder(
final LocTextKey label, final String name,
final String value, final LocTextKey label,
final Collection<String> supportedFiles) { final String value,
final Collection<String> supportedFiles) {
super(name, label, value);
this.supportedFiles = supportedFiles; super(name, label, value);
} this.supportedFiles = supportedFiles;
}
@Override
void build(final FormBuilder builder) { @Override
final Label titleLabel = createTitleLabel(builder.formParent, builder, this); void build(final FormBuilder builder) {
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput); final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
final FileUploadSelection fileUpload = builder.widgetFactory.fileUploadSelection( final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
fieldGrid, final FileUploadSelection fileUpload = builder.widgetFactory.fileUploadSelection(
builder.readonly || this.readonly, fieldGrid,
this.supportedFiles); builder.readonly || this.readonly,
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); this.supportedFiles);
fileUpload.setLayoutData(gridData); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
fileUpload.setFileName(this.value); fileUpload.setLayoutData(gridData);
fileUpload.setFileName(this.value);
final Label errorLabel = createErrorLabel(fieldGrid);
builder.form.putField(this.name, titleLabel, fileUpload, errorLabel); final Label errorLabel = createErrorLabel(fieldGrid);
builder.setFieldVisible(this.visible, this.name); builder.form.putField(this.name, titleLabel, fileUpload, errorLabel);
} builder.setFieldVisible(this.visible, this.name);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,294 +1,306 @@
/* /*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.form; package ch.ethz.seb.sebserver.gui.form;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.function.Supplier; import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT; import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.eclipse.swt.layout.GridData; import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Label; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.TabItem; import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger; import org.eclipse.swt.widgets.Label;
import org.slf4j.LoggerFactory; import org.eclipse.swt.widgets.TabItem;
import org.slf4j.Logger;
import ch.ethz.seb.sebserver.gbl.model.Entity; import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.widget.Selection; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.widget.Selection;
public class FormBuilder { import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
public static final int FORM_ROW_HEIGHT = 25; public class FormBuilder {
private static final Logger log = LoggerFactory.getLogger(FormBuilder.class); public static final int FORM_ROW_HEIGHT = 25;
final I18nSupport i18nSupport; private static final Logger log = LoggerFactory.getLogger(FormBuilder.class);
final PageService pageService;
final WidgetFactory widgetFactory; final Cryptor cryptor;
public final PageContext pageContext; final I18nSupport i18nSupport;
public final Composite formParent; final PageService pageService;
public final Form form; final WidgetFactory widgetFactory;
public final PageContext pageContext;
boolean readonly = false; public final Composite formParent;
private int defaultSpanLabel = 2; public final Form form;
private int defaultSpanInput = 5;
private int defaultSpanEmptyCell = 1; boolean readonly = false;
private boolean emptyCellSeparation = true; private int defaultSpanLabel = 2;
private int defaultSpanInput = 5;
public FormBuilder( private int defaultSpanEmptyCell = 1;
final PageService pageService, private boolean emptyCellSeparation = true;
final PageContext pageContext,
final int rows) { public FormBuilder(
final PageService pageService,
this.i18nSupport = pageService.getI18nSupport(); final PageContext pageContext,
this.pageService = pageService; final Cryptor cryptor,
this.widgetFactory = pageService.getWidgetFactory(); final int rows) {
this.pageContext = pageContext;
this.form = new Form(pageService.getJSONMapper()); this.cryptor = cryptor;
this.i18nSupport = pageService.getI18nSupport();
this.formParent = this.widgetFactory.formGrid( this.pageService = pageService;
pageContext.getParent(), this.widgetFactory = pageService.getWidgetFactory();
rows); this.pageContext = pageContext;
} this.form = new Form(pageService.getJSONMapper(), cryptor);
public FormBuilder readonly(final boolean readonly) { this.formParent = this.widgetFactory.formGrid(
this.readonly = readonly; pageContext.getParent(),
return this; rows);
} }
public FormBuilder setVisible(final boolean visible, final String group) { public FormBuilder readonly(final boolean readonly) {
this.form.setVisible(visible, group); this.readonly = readonly;
return this; return this;
} }
public void setFieldVisible(final boolean visible, final String fieldName) { public FormBuilder setVisible(final boolean visible, final String group) {
this.form.setFieldVisible(visible, fieldName); this.form.setVisible(visible, group);
return this;
} }
public FormBuilder setControl(final TabItem instTab) { public void setFieldVisible(final boolean visible, final String fieldName) {
instTab.setControl(this.formParent); this.form.setFieldVisible(visible, fieldName);
return this;
} }
public FormBuilder withDefaultSpanLabel(final int span) { public FormBuilder setControl(final TabItem instTab) {
this.defaultSpanLabel = span; instTab.setControl(this.formParent);
return this; return this;
} }
public FormBuilder withDefaultSpanInput(final int span) { public FormBuilder withDefaultSpanLabel(final int span) {
this.defaultSpanInput = span; this.defaultSpanLabel = span;
return this; return this;
} }
public FormBuilder withDefaultSpanEmptyCell(final int span) { public FormBuilder withDefaultSpanInput(final int span) {
this.defaultSpanEmptyCell = span; this.defaultSpanInput = span;
return this; return this;
} }
public FormBuilder withEmptyCellSeparation(final boolean separation) { public FormBuilder withDefaultSpanEmptyCell(final int span) {
this.emptyCellSeparation = separation; this.defaultSpanEmptyCell = span;
return this; return this;
} }
public FormBuilder addEmptyCellIf(final BooleanSupplier condition) { public FormBuilder withEmptyCellSeparation(final boolean separation) {
if (condition != null && condition.getAsBoolean()) { this.emptyCellSeparation = separation;
return addEmptyCell(); return this;
} }
return this;
} public FormBuilder addEmptyCellIf(final BooleanSupplier condition) {
if (condition != null && condition.getAsBoolean()) {
public FormBuilder addEmptyCell() { return addEmptyCell();
return addEmptyCell(1); }
} return this;
}
public FormBuilder addEmptyCell(final int span) {
empty(this.formParent, span, 1); public FormBuilder addEmptyCell() {
return this; return addEmptyCell(1);
} }
public FormBuilder putStaticValueIf(final BooleanSupplier condition, final String name, final String value) { public FormBuilder addEmptyCell(final int span) {
if (condition != null && condition.getAsBoolean()) { empty(this.formParent, span, 1);
return putStaticValue(name, value); return this;
} }
return this; public FormBuilder putStaticValueIf(final BooleanSupplier condition, final String name, final String value) {
} if (condition != null && condition.getAsBoolean()) {
return putStaticValue(name, value);
public FormBuilder putStaticValue(final String name, final String value) { }
try {
this.form.putStatic(name, value); return this;
} catch (final Exception e) { }
log.error("Failed to put static field value to json object: ", e);
} public FormBuilder putStaticValue(final String name, final String value) {
return this; try {
} this.form.putStatic(name, value);
} catch (final Exception e) {
public FormBuilder addFieldIf( log.error("Failed to put static field value to json object: ", e);
final BooleanSupplier condition, }
final Supplier<FieldBuilder<?>> templateSupplier) { return this;
}
if (condition.getAsBoolean()) {
return addField(templateSupplier.get()); public FormBuilder addFieldIf(
} final BooleanSupplier condition,
final Supplier<FieldBuilder<?>> templateSupplier) {
return this;
} if (condition.getAsBoolean()) {
return addField(templateSupplier.get());
public FormBuilder addField(final FieldBuilder<?> template) { }
template.spanLabel = (template.spanLabel < 0) ? this.defaultSpanLabel : template.spanLabel;
template.spanInput = (template.spanInput < 0) ? this.defaultSpanInput : template.spanInput; return this;
template.spanEmptyCell = (template.spanEmptyCell < 0) ? this.defaultSpanEmptyCell : template.spanEmptyCell; }
template.autoEmptyCellSeparation = (template.autoEmptyCellSeparation != null)
? template.autoEmptyCellSeparation public FormBuilder addField(final FieldBuilder<?> template) {
: this.emptyCellSeparation; template.spanLabel = (template.spanLabel < 0) ? this.defaultSpanLabel : template.spanLabel;
template.spanInput = (template.spanInput < 0) ? this.defaultSpanInput : template.spanInput;
if (template.autoEmptyCellSeparation && this.form.hasFields()) { template.spanEmptyCell = (template.spanEmptyCell < 0) ? this.defaultSpanEmptyCell : template.spanEmptyCell;
addEmptyCell(template.spanEmptyCell); template.autoEmptyCellSeparation = (template.autoEmptyCellSeparation != null)
} ? template.autoEmptyCellSeparation
: this.emptyCellSeparation;
template.build(this);
if (template.autoEmptyCellSeparation && this.form.hasFields()) {
if (StringUtils.isNotBlank(template.group)) { addEmptyCell(template.spanEmptyCell);
this.form.addToGroup(template.group, template.name); }
}
template.build(this);
return this;
} if (StringUtils.isNotBlank(template.group)) {
this.form.addToGroup(template.group, template.name);
public <T extends Entity> FormHandle<T> build() { }
return buildFor(null);
} return this;
}
public <T extends Entity> FormHandle<T> buildFor(final RestCall<T> post) {
return new FormHandle<>( public <T extends Entity> FormHandle<T> build() {
this.pageService, return buildFor(null);
this.pageContext, }
this.form,
post); public <T extends Entity> FormHandle<T> buildFor(final RestCall<T> post) {
} return new FormHandle<>(
this.pageService,
private void empty(final Composite parent, final int hspan, final int vspan) { this.pageContext,
final Label empty = new Label(parent, SWT.LEFT); this.form,
final GridData gridData = new GridData(SWT.LEFT, SWT.TOP, false, false, hspan, vspan); post);
gridData.minimumWidth = 0; }
gridData.widthHint = 0;
empty.setLayoutData(gridData); private void empty(final Composite parent, final int hspan, final int vspan) {
} final Label empty = new Label(parent, SWT.LEFT);
final GridData gridData = new GridData(SWT.LEFT, SWT.TOP, false, false, hspan, vspan);
public static CheckboxFieldBuilder checkbox(final String name, final LocTextKey label) { gridData.minimumWidth = 0;
return new CheckboxFieldBuilder(name, label, null); gridData.widthHint = 0;
} empty.setLayoutData(gridData);
}
public static CheckboxFieldBuilder checkbox(final String name, final LocTextKey label, final String value) {
return new CheckboxFieldBuilder(name, label, value); public static CheckboxFieldBuilder checkbox(final String name, final LocTextKey label) {
} return new CheckboxFieldBuilder(name, label, null);
}
public static TextFieldBuilder text(final String name) {
return new TextFieldBuilder(name, null, null); public static CheckboxFieldBuilder checkbox(final String name, final LocTextKey label, final String value) {
} return new CheckboxFieldBuilder(name, label, value);
}
public static TextFieldBuilder text(final String name, final LocTextKey label) {
return new TextFieldBuilder(name, label, null); public static TextFieldBuilder text(final String name) {
} return new TextFieldBuilder(name, null, null);
}
public static TextFieldBuilder text(final String name, final LocTextKey label, final String value) {
return new TextFieldBuilder(name, label, value); public static TextFieldBuilder text(final String name, final LocTextKey label) {
} return new TextFieldBuilder(name, label, null);
}
public static TextFieldBuilder text(final String name, final LocTextKey label,
final Supplier<String> valueSupplier) { public static TextFieldBuilder text(final String name, final LocTextKey label, final String value) {
return new TextFieldBuilder(name, label, valueSupplier.get()); return new TextFieldBuilder(name, label, value);
} }
public static SelectionFieldBuilder singleSelection( public static TextFieldBuilder text(
final String name, final String name,
final LocTextKey label, final LocTextKey label,
final String value, final Supplier<String> valueSupplier) {
final Supplier<List<Tuple<String>>> itemsSupplier) {
return new TextFieldBuilder(name, label, valueSupplier.get());
return new SelectionFieldBuilder(Selection.Type.SINGLE, name, label, value, itemsSupplier); }
}
public static PasswordFieldBuilder password(final String name, final LocTextKey label, final CharSequence value) {
public static SelectionFieldBuilder multiSelection( return new PasswordFieldBuilder(name, label, value);
final String name, }
final LocTextKey label,
final String value, public static SelectionFieldBuilder singleSelection(
final Supplier<List<Tuple<String>>> itemsSupplier) { final String name,
final LocTextKey label,
return new SelectionFieldBuilder(Selection.Type.MULTI, name, label, value, itemsSupplier); final String value,
} final Supplier<List<Tuple<String>>> itemsSupplier) {
public static SelectionFieldBuilder multiCheckboxSelection( return new SelectionFieldBuilder(Selection.Type.SINGLE, name, label, value, itemsSupplier);
final String name, }
final LocTextKey label,
final String value, public static SelectionFieldBuilder multiSelection(
final Supplier<List<Tuple<String>>> itemsSupplier) { final String name,
final LocTextKey label,
return new SelectionFieldBuilder(Selection.Type.MULTI_CHECKBOX, name, label, value, itemsSupplier); final String value,
} final Supplier<List<Tuple<String>>> itemsSupplier) {
public static SelectionFieldBuilder multiComboSelection( return new SelectionFieldBuilder(Selection.Type.MULTI, name, label, value, itemsSupplier);
final String name, }
final LocTextKey label,
final String value, public static SelectionFieldBuilder multiCheckboxSelection(
final Supplier<List<Tuple<String>>> itemsSupplier) { final String name,
final LocTextKey label,
return new SelectionFieldBuilder(Selection.Type.MULTI_COMBO, name, label, value, itemsSupplier); final String value,
} final Supplier<List<Tuple<String>>> itemsSupplier) {
public static SelectionFieldBuilder colorSelection( return new SelectionFieldBuilder(Selection.Type.MULTI_CHECKBOX, name, label, value, itemsSupplier);
final String name, }
final LocTextKey label,
final String value) { public static SelectionFieldBuilder multiComboSelection(
final String name,
return new SelectionFieldBuilder(Selection.Type.COLOR, name, label, value, null); final LocTextKey label,
} final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) {
public static ThresholdListBuilder thresholdList(
final String name, return new SelectionFieldBuilder(Selection.Type.MULTI_COMBO, name, label, value, itemsSupplier);
final LocTextKey label, }
final Indicator indicator) {
public static SelectionFieldBuilder colorSelection(
return new ThresholdListBuilder( final String name,
name, final LocTextKey label,
label, final String value) {
indicator.thresholds);
} return new SelectionFieldBuilder(Selection.Type.COLOR, name, label, value, null);
}
public static ImageUploadFieldBuilder imageUpload(final String name, final LocTextKey label, final String value) {
return new ImageUploadFieldBuilder(name, label, value); public static ThresholdListBuilder thresholdList(
} final String name,
final LocTextKey label,
public static FileUploadFieldBuilder fileUpload( final Indicator indicator) {
final String name,
final LocTextKey label, return new ThresholdListBuilder(
final String value, name,
final String... supportedFiles) { label,
indicator.thresholds);
return new FileUploadFieldBuilder( }
name,
label, public static ImageUploadFieldBuilder imageUpload(final String name, final LocTextKey label, final String value) {
value, return new ImageUploadFieldBuilder(name, label, value);
(supportedFiles != null) ? Arrays.asList(supportedFiles) : Collections.emptyList()); }
}
public static FileUploadFieldBuilder fileUpload(
} final String name,
final LocTextKey label,
final String value,
final String... supportedFiles) {
return new FileUploadFieldBuilder(
name,
label,
value,
(supportedFiles != null) ? Arrays.asList(supportedFiles) : Collections.emptyList());
}
}

View file

@ -1,57 +1,58 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.form; package ch.ethz.seb.sebserver.gui.form;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.ImageUploadSelection; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.ImageUploadSelection;
public final class ImageUploadFieldBuilder extends FieldBuilder<String> {
public final class ImageUploadFieldBuilder extends FieldBuilder<String> {
private int maxWidth = 100;
private int maxHeight = 100; private int maxWidth = 100;
private int maxHeight = 100;
ImageUploadFieldBuilder(final String name, final LocTextKey label, final String value) {
super(name, label, value); ImageUploadFieldBuilder(final String name, final LocTextKey label, final String value) {
} super(name, label, value);
}
public ImageUploadFieldBuilder withMaxWidth(final int width) {
this.maxWidth = width; public ImageUploadFieldBuilder withMaxWidth(final int width) {
return this; this.maxWidth = width;
} return this;
}
public ImageUploadFieldBuilder withMaxHeight(final int height) {
this.maxHeight = height; public ImageUploadFieldBuilder withMaxHeight(final int height) {
return this; this.maxHeight = height;
} return this;
}
@Override
void build(final FormBuilder builder) { @Override
final Label titleLabel = createTitleLabel(builder.formParent, builder, this); void build(final FormBuilder builder) {
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput); final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
final ImageUploadSelection imageUpload = builder.widgetFactory.imageUploadLocalized( final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
fieldGrid, final ImageUploadSelection imageUpload = builder.widgetFactory.imageUploadLocalized(
new LocTextKey("sebserver.overall.upload"), fieldGrid,
builder.readonly || this.readonly, new LocTextKey("sebserver.overall.upload"),
this.maxWidth, builder.readonly || this.readonly,
this.maxHeight); this.maxWidth,
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); this.maxHeight);
imageUpload.setLayoutData(gridData); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
imageUpload.setImageBase64(this.value); imageUpload.setLayoutData(gridData);
imageUpload.setImageBase64(this.value);
final Label errorLabel = createErrorLabel(fieldGrid);
builder.form.putField(this.name, titleLabel, imageUpload, errorLabel); final Label errorLabel = createErrorLabel(fieldGrid);
builder.setFieldVisible(this.visible, this.name); builder.form.putField(this.name, titleLabel, imageUpload, errorLabel);
} builder.setFieldVisible(this.visible, this.name);
}
} }

View file

@ -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);
}
}

View file

@ -1,150 +1,150 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.form; package ch.ethz.seb.sebserver.gui.form;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Text;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService; import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
import ch.ethz.seb.sebserver.gui.widget.Selection; import ch.ethz.seb.sebserver.gui.widget.Selection;
import ch.ethz.seb.sebserver.gui.widget.Selection.Type; import ch.ethz.seb.sebserver.gui.widget.Selection.Type;
public final class SelectionFieldBuilder extends FieldBuilder<String> { public final class SelectionFieldBuilder extends FieldBuilder<String> {
final Supplier<List<Tuple<String>>> itemsSupplier; final Supplier<List<Tuple<String>>> itemsSupplier;
Consumer<Form> selectionListener = null; Consumer<Form> selectionListener = null;
final Selection.Type type; final Selection.Type type;
SelectionFieldBuilder( SelectionFieldBuilder(
final Selection.Type type, final Selection.Type type,
final String name, final String name,
final LocTextKey label, final LocTextKey label,
final String value, final String value,
final Supplier<List<Tuple<String>>> itemsSupplier) { final Supplier<List<Tuple<String>>> itemsSupplier) {
super(name, label, value); super(name, label, value);
this.type = type; this.type = type;
this.itemsSupplier = itemsSupplier; this.itemsSupplier = itemsSupplier;
} }
public SelectionFieldBuilder withSelectionListener(final Consumer<Form> selectionListener) { public SelectionFieldBuilder withSelectionListener(final Consumer<Form> selectionListener) {
this.selectionListener = selectionListener; this.selectionListener = selectionListener;
return this; return this;
} }
@Override @Override
void build(final FormBuilder builder) { 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) { if (builder.readonly || this.readonly) {
buildReadOnly(builder, titleLabel); buildReadOnly(builder, titleLabel);
} else { } else {
buildInput(builder, titleLabel); buildInput(builder, titleLabel);
} }
} }
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 Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final String actionKey = (this.label != null) ? this.label.name + ".action" : null; final String actionKey = (this.label != null) ? this.label.name + ".action" : null;
final Selection selection = builder.widgetFactory.selectionLocalized( final Selection selection = builder.widgetFactory.selectionLocalized(
this.type, this.type,
fieldGrid, fieldGrid,
this.itemsSupplier, this.itemsSupplier,
null, null,
null, null,
actionKey); actionKey);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
((Control) selection).setLayoutData(gridData); ((Control) selection).setLayoutData(gridData);
selection.select(this.value); selection.select(this.value);
final Label errorLabel = createErrorLabel(fieldGrid); final Label errorLabel = createErrorLabel(fieldGrid);
builder.form.putField(this.name, titleLabel, selection, errorLabel); builder.form.putField(this.name, titleLabel, selection, errorLabel);
if (this.selectionListener != null) { if (this.selectionListener != null) {
((Control) selection).addListener(SWT.Selection, e -> { ((Control) selection).addListener(SWT.Selection, e -> {
this.selectionListener.accept(builder.form); this.selectionListener.accept(builder.form);
}); });
} }
builder.setFieldVisible(this.visible, this.name); builder.setFieldVisible(this.visible, this.name);
} }
/* Build the read-only representation of the selection field */ /* 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) { 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 Composite composite = new Composite(builder.formParent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(1, true); final GridLayout gridLayout = new GridLayout(1, true);
//gridLayout.verticalSpacing = 5; //gridLayout.verticalSpacing = 5;
gridLayout.marginBottom = 5; gridLayout.marginBottom = 5;
gridLayout.horizontalSpacing = 0; gridLayout.horizontalSpacing = 0;
gridLayout.marginLeft = 0; gridLayout.marginLeft = 0;
gridLayout.marginHeight = 0; gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0; gridLayout.marginWidth = 0;
composite.setLayout(gridLayout); composite.setLayout(gridLayout);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1)); composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, this.spanInput, 1));
if (StringUtils.isBlank(this.value)) { if (StringUtils.isBlank(this.value)) {
final Label label = new Label(composite, SWT.NONE); final Label label = new Label(composite, SWT.NONE);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true); final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
label.setLayoutData(gridData); label.setLayoutData(gridData);
label.setText(this.value); label.setText(this.value);
} else { } else {
final Collection<String> keys = Arrays.asList(StringUtils.split(this.value, Constants.LIST_SEPARATOR)); final Collection<String> keys = Arrays.asList(StringUtils.split(this.value, Constants.LIST_SEPARATOR));
this.itemsSupplier.get() this.itemsSupplier.get()
.stream() .stream()
.filter(tuple -> keys.contains(tuple._1)) .filter(tuple -> keys.contains(tuple._1))
.map(tuple -> tuple._1) .map(tuple -> tuple._1)
.forEach(v -> buildReadonlyLabel(composite, v, 1)); .forEach(v -> buildReadonlyLabel(composite, v, 1));
} }
} else { } else {
builder.form.putReadonlyField( builder.form.putReadonlyField(
this.name, this.name,
titleLabel, titleLabel,
buildReadonlyLabel(builder.formParent, this.value, this.spanInput)); buildReadonlyLabel(builder.formParent, this.value, this.spanInput));
builder.setFieldVisible(this.visible, this.name); builder.setFieldVisible(this.visible, this.name);
} }
} }
private Text buildReadonlyLabel(final Composite composite, final String valueKey, final int hspan) { private Text buildReadonlyLabel(final Composite composite, final String valueKey, final int hspan) {
final Text label = new Text(composite, SWT.READ_ONLY); final Text label = new Text(composite, SWT.READ_ONLY);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true, hspan, 1); final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true, hspan, 1);
gridData.verticalIndent = 0; gridData.verticalIndent = 0;
gridData.horizontalIndent = 0; gridData.horizontalIndent = 0;
label.setLayoutData(gridData); label.setLayoutData(gridData);
final Supplier<String> valueSupplier = () -> this.itemsSupplier.get().stream() final Supplier<String> valueSupplier = () -> this.itemsSupplier.get().stream()
.filter(tuple -> valueKey.equals(tuple._1)) .filter(tuple -> valueKey.equals(tuple._1))
.findFirst() .findFirst()
.map(tuple -> tuple._2) .map(tuple -> tuple._2)
.orElse(Constants.EMPTY_NOTE); .orElse(Constants.EMPTY_NOTE);
final Consumer<Text> updateFunction = t -> t.setText(valueSupplier.get()); final Consumer<Text> updateFunction = t -> t.setText(valueSupplier.get());
label.setText(valueSupplier.get()); label.setText(valueSupplier.get());
label.setData(PolyglotPageService.POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction); label.setData(PolyglotPageService.POLYGLOT_WIDGET_FUNCTION_KEY, updateFunction);
return label; return label;
} }
} }

View file

@ -18,6 +18,7 @@ import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Text;
@ -88,7 +89,7 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
@Override @Override
void build(final FormBuilder builder) { void build(final FormBuilder builder) {
final boolean readonly = builder.readonly || this.readonly; 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 Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
if (readonly && this.isHTML) { if (readonly && this.isHTML) {
@ -117,7 +118,7 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
gridData.minimumHeight = this.areaMinHeight; gridData.minimumHeight = this.areaMinHeight;
} else if (this.isColorBox) { } else if (this.isColorBox) {
gridData.minimumHeight = WidgetFactory.TEXT_INPUT_MIN_HEIGHT; 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); textInput.setLayoutData(gridData);
if (StringUtils.isNoneBlank(this.value)) { if (StringUtils.isNoneBlank(this.value)) {

View file

@ -1,89 +1,90 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.form; package ch.ethz.seb.sebserver.gui.form;
import java.util.Collection; import java.util.Collection;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gui.widget.ThresholdList; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.widget.ThresholdList;
public class ThresholdListBuilder extends FieldBuilder<Collection<Threshold>> {
public class ThresholdListBuilder extends FieldBuilder<Collection<Threshold>> {
private final Collection<Threshold> thresholds;
private final Collection<Threshold> thresholds;
protected ThresholdListBuilder(
final String name, protected ThresholdListBuilder(
final LocTextKey label, final String name,
final Collection<Threshold> thresholds) { final LocTextKey label,
final Collection<Threshold> thresholds) {
super(name, label, thresholds);
this.thresholds = thresholds; super(name, label, thresholds);
} this.thresholds = thresholds;
}
@Override
void build(final FormBuilder builder) { @Override
final Label titleLabel = createTitleLabel(builder.formParent, builder, this); void build(final FormBuilder builder) {
if (builder.readonly || this.readonly) { final Control titleLabel = createTitleLabel(builder.formParent, builder, this);
// No read-only view needed for this so far? if (builder.readonly || this.readonly) {
return; // No read-only view needed for this so far?
} else { return;
} else {
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final Composite fieldGrid = createFieldGrid(builder.formParent, this.spanInput);
final ThresholdList thresholdList = builder.widgetFactory.thresholdList(
fieldGrid, final ThresholdList thresholdList = builder.widgetFactory.thresholdList(
fieldGrid.getParent().getParent(), fieldGrid,
this.thresholds, fieldGrid.getParent().getParent(),
() -> { this.thresholds,
try { () -> {
final String fieldValue = builder.form.getFieldValue(Domain.INDICATOR.ATTR_TYPE); try {
return IndicatorType.valueOf(fieldValue); final String fieldValue = builder.form.getFieldValue(Domain.INDICATOR.ATTR_TYPE);
} catch (final Exception e) { return IndicatorType.valueOf(fieldValue);
return null; } catch (final Exception e) {
} return null;
}); }
});
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
thresholdList.setLayoutData(gridData); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
thresholdList.setLayoutData(gridData);
final Label errorLabel = createErrorLabel(fieldGrid);
builder.form.putField(this.name, titleLabel, thresholdList, errorLabel); final Label errorLabel = createErrorLabel(fieldGrid);
builder.setFieldVisible(this.visible, this.name); builder.form.putField(this.name, titleLabel, thresholdList, errorLabel);
} builder.setFieldVisible(this.visible, this.name);
}
}
}
public static final String thresholdsToFormURLEncodedStringValue(final Collection<Threshold> thresholds) {
if (thresholds == null || thresholds.isEmpty()) { public static final String thresholdsToFormURLEncodedStringValue(final Collection<Threshold> thresholds) {
return null; if (thresholds == null || thresholds.isEmpty()) {
} return null;
}
// thresholds={value}|{color},thresholds={value}|{color}...
return StringUtils.join(thresholds.stream() // thresholds={value}|{color},thresholds={value}|{color}...
.map(t -> Domain.THRESHOLD.REFERENCE_NAME return StringUtils.join(thresholds.stream()
+ Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR .map(t -> Domain.THRESHOLD.REFERENCE_NAME
+ String.valueOf(t.getValue()) + Constants.FORM_URL_ENCODED_NAME_VALUE_SEPARATOR
+ Constants.EMBEDDED_LIST_SEPARATOR + String.valueOf(t.getValue())
+ t.getColor()) + Constants.EMBEDDED_LIST_SEPARATOR
.collect(Collectors.toList()), + t.getColor())
Constants.LIST_SEPARATOR); .collect(Collectors.toList()),
} Constants.LIST_SEPARATOR);
}
}
}

View file

@ -20,6 +20,7 @@ import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; 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.Tuple3;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import org.apache.commons.lang3.StringUtils; 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 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_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_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( public static final EnumSet<AttributeType> ATTRIBUTE_TYPES_NOT_DISPLAYED = EnumSet.of(
AttributeType.LABEL, AttributeType.LABEL,
@ -656,4 +658,16 @@ public class ResourceService {
.call(); .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());
}
} }

View file

@ -1,178 +1,181 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.service.examconfig.impl; package ch.ethz.seb.sebserver.gui.service.examconfig.impl;
import java.nio.charset.StandardCharsets; import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
import java.security.MessageDigest; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import java.security.NoSuchAlgorithmException; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import org.apache.commons.codec.binary.Hex; import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.eclipse.rap.rwt.RWT; import ch.ethz.seb.sebserver.gui.form.FieldBuilder;
import org.eclipse.swt.SWT; import ch.ethz.seb.sebserver.gui.service.examconfig.InputField;
import org.eclipse.swt.layout.GridData; import ch.ethz.seb.sebserver.gui.service.examconfig.InputFieldBuilder;
import org.eclipse.swt.widgets.Composite; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import org.eclipse.swt.widgets.Label; import ch.ethz.seb.sebserver.gui.widget.PasswordInput;
import org.eclipse.swt.widgets.Listener; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import org.eclipse.swt.widgets.Text; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
import org.slf4j.Logger; import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory; import org.eclipse.rap.rwt.RWT;
import org.springframework.context.annotation.Lazy; import org.eclipse.swt.SWT;
import org.springframework.stereotype.Component; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; import org.eclipse.swt.widgets.Label;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; import org.eclipse.swt.widgets.Listener;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Orientation; import org.slf4j.Logger;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gui.form.FieldBuilder; import org.springframework.context.annotation.Lazy;
import ch.ethz.seb.sebserver.gui.service.examconfig.InputField; import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gui.service.examconfig.InputFieldBuilder;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; @Lazy
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @Component
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; @GuiProfile
public class PasswordFieldBuilder implements InputFieldBuilder {
@Lazy
@Component private static final Logger log = LoggerFactory.getLogger(PasswordFieldBuilder.class);
@GuiProfile
public class PassworFieldBuilder implements InputFieldBuilder { private static final LocTextKey VAL_CONFIRM_PWD_TEXT_KEY =
new LocTextKey("sebserver.examconfig.props.validation.password.confirm");
private static final Logger log = LoggerFactory.getLogger(PassworFieldBuilder.class);
private final Cryptor cryptor;
private static final LocTextKey VAL_CONFIRM_PWD_TEXT_KEY = private final WidgetFactory widgetFactory;
new LocTextKey("sebserver.examconfig.props.validation.password.confirm");
public PasswordFieldBuilder(
@Override final WidgetFactory widgetFactory,
public boolean builderFor( final Cryptor cryptor) {
final ConfigurationAttribute attribute,
final Orientation orientation) { this.cryptor = cryptor;
this.widgetFactory = widgetFactory;
if (attribute == null) { }
return false;
} @Override
public boolean builderFor(
return AttributeType.PASSWORD_FIELD == attribute.type; final ConfigurationAttribute attribute,
} final Orientation orientation) {
@Override if (attribute == null) {
public InputField createInputField( return false;
final Composite parent, }
final ConfigurationAttribute attribute,
final ViewContext viewContext) { return AttributeType.PASSWORD_FIELD == attribute.type;
}
final Orientation orientation = viewContext
.getOrientation(attribute.id); @Override
final Composite innerGrid = InputFieldBuilder public InputField createInputField(
.createInnerGrid(parent, attribute, orientation); final Composite parent,
final ConfigurationAttribute attribute,
final Text passwordInput = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.PASSWORD); final ViewContext viewContext) {
final GridData passwordInputLD = new GridData(SWT.FILL, SWT.FILL, true, false);
passwordInput.setLayoutData(passwordInputLD); final Orientation orientation = viewContext
final Text confirmInput = new Text(innerGrid, SWT.LEFT | SWT.BORDER | SWT.PASSWORD); .getOrientation(attribute.id);
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); final Composite innerGrid = InputFieldBuilder
gridData.verticalIndent = 14; .createInnerGrid(parent, attribute, orientation);
confirmInput.setLayoutData(gridData);
final PasswordInput passwordInput = new PasswordInput(innerGrid, widgetFactory);
final PasswordInputField passwordInputField = new PasswordInputField( final GridData passwordInputLD = new GridData(SWT.FILL, SWT.FILL, true, true);
attribute, passwordInput.setLayoutData(passwordInputLD);
orientation,
passwordInput, final PasswordInput confirmInput = new PasswordInput(innerGrid, widgetFactory);
confirmInput, final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
FieldBuilder.createErrorLabel(innerGrid)); gridData.verticalIndent = 14;
confirmInput.setLayoutData(gridData);
if (viewContext.readonly) { innerGrid.setData("isPlainText", false);
passwordInput.setEditable(false);
passwordInput.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key); final PasswordInputField passwordInputField = new PasswordInputField(
passwordInputLD.heightHint = WidgetFactory.TEXT_INPUT_MIN_HEIGHT; attribute,
confirmInput.setEditable(false); orientation,
confirmInput.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key); passwordInput,
gridData.heightHint = WidgetFactory.TEXT_INPUT_MIN_HEIGHT; confirmInput,
} else { FieldBuilder.createErrorLabel(innerGrid),
final Listener valueChangeEventListener = event -> { cryptor);
passwordInputField.clearError();
if (viewContext.readonly) {
final String pwd = passwordInput.getText(); passwordInput.setEditable(false);
final String confirm = confirmInput.getText(); passwordInputLD.heightHint = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
confirmInput.setEditable(false);
if (passwordInputField.initValue != null && passwordInputField.initValue.equals(pwd)) { confirmInput.setData(RWT.CUSTOM_VARIANT, CustomVariant.CONFIG_INPUT_READONLY.key);
return; gridData.heightHint = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
} } else {
final Listener valueChangeEventListener = event -> {
if (!pwd.equals(confirm)) { passwordInputField.clearError();
passwordInputField.showError(viewContext
.getI18nSupport() final CharSequence pwd = passwordInput.getValue();
.getText(VAL_CONFIRM_PWD_TEXT_KEY)); final CharSequence confirm = confirmInput.getValue();
return;
} if (passwordInputField.initValue != null && passwordInputField.initValue.equals(pwd)) {
return;
final String hashedPWD = passwordInputField.getValue(); }
if (hashedPWD != null) {
viewContext.getValueChangeListener().valueChanged( if (!pwd.equals(confirm)) {
viewContext, passwordInputField.showError(viewContext
attribute, .getI18nSupport()
hashedPWD, .getText(VAL_CONFIRM_PWD_TEXT_KEY));
passwordInputField.listIndex); return;
} }
};
final String hashedPWD = passwordInputField.getValue();
passwordInput.addListener(SWT.FocusOut, valueChangeEventListener); if (hashedPWD != null) {
passwordInput.addListener(SWT.Traverse, valueChangeEventListener); viewContext.getValueChangeListener().valueChanged(
confirmInput.addListener(SWT.FocusOut, valueChangeEventListener); viewContext,
confirmInput.addListener(SWT.Traverse, valueChangeEventListener); attribute,
} hashedPWD,
return passwordInputField; passwordInputField.listIndex);
} }
};
static final class PasswordInputField extends AbstractInputField<Text> {
passwordInput.addListener(SWT.FocusOut, valueChangeEventListener);
private final Text confirm; passwordInput.addListener(SWT.Traverse, valueChangeEventListener);
confirmInput.addListener(SWT.FocusOut, valueChangeEventListener);
PasswordInputField( confirmInput.addListener(SWT.Traverse, valueChangeEventListener);
final ConfigurationAttribute attribute, }
final Orientation orientation, return passwordInputField;
final Text control, }
final Text confirm,
final Label errorLabel) { static final class PasswordInputField extends AbstractInputField<PasswordInput> {
super(attribute, orientation, control, errorLabel); private final PasswordInput confirm;
this.confirm = confirm; private final Cryptor cryptor;
}
PasswordInputField(
@Override final ConfigurationAttribute attribute,
protected void setValueToControl(final String value) { final Orientation orientation,
// TODO clarify setting some "fake" input when a password is set (like in config tool) final PasswordInput control,
if (value != null) { final PasswordInput confirm,
this.control.setText(value); final Label errorLabel,
this.confirm.setText(value); final Cryptor cryptor) {
}
} super(attribute, orientation, control, errorLabel);
this.confirm = confirm;
@Override this.cryptor = cryptor;
public String getValue() { }
String hashedPWD;
try { @Override
hashedPWD = hashPassword(this.control.getText()); protected void setValueToControl(final String value) {
} catch (final NoSuchAlgorithmException e) { if (StringUtils.isNotBlank(value)) {
log.error("Failed to hash password: ", e); CharSequence pwd = cryptor.decrypt(value);
showError("Failed to hash password"); this.control.setValue(pwd.toString());
hashedPWD = null; this.confirm.setValue(pwd.toString());
} } else {
this.control.setValue(StringUtils.EMPTY);
return hashedPWD; this.confirm.setValue(StringUtils.EMPTY);
} }
}
private String hashPassword(final String pwd) throws NoSuchAlgorithmException {
final MessageDigest digest = MessageDigest.getInstance("SHA-256"); @Override
final byte[] encodedhash = digest.digest( public String getValue() {
pwd.getBytes(StandardCharsets.UTF_8)); final CharSequence pwd = this.control.getValue();
if (StringUtils.isNotBlank(pwd)) {
return Hex.encodeHexString(encodedhash); return cryptor.encrypt(pwd).toString();
} }
} return StringUtils.EMPTY;
}
}
}
}

View file

@ -20,6 +20,7 @@ import java.util.function.Supplier;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.RWT;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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 String ATTR_PAGE_STATE = "PAGE_STATE";
private static final ListenerComparator LIST_COMPARATOR = new ListenerComparator(); private static final ListenerComparator LIST_COMPARATOR = new ListenerComparator();
private final Cryptor cryptor;
private final JSONMapper jsonMapper; private final JSONMapper jsonMapper;
private final WidgetFactory widgetFactory; private final WidgetFactory widgetFactory;
private final PolyglotPageService polyglotPageService; private final PolyglotPageService polyglotPageService;
@ -85,12 +87,14 @@ public class PageServiceImpl implements PageService {
private final CurrentUser currentUser; private final CurrentUser currentUser;
public PageServiceImpl( public PageServiceImpl(
final Cryptor cryptor,
final JSONMapper jsonMapper, final JSONMapper jsonMapper,
final WidgetFactory widgetFactory, final WidgetFactory widgetFactory,
final PolyglotPageService polyglotPageService, final PolyglotPageService polyglotPageService,
final ResourceService resourceService, final ResourceService resourceService,
final CurrentUser currentUser) { final CurrentUser currentUser) {
this.cryptor = cryptor;
this.jsonMapper = jsonMapper; this.jsonMapper = jsonMapper;
this.widgetFactory = widgetFactory; this.widgetFactory = widgetFactory;
this.polyglotPageService = polyglotPageService; this.polyglotPageService = polyglotPageService;
@ -337,7 +341,7 @@ public class PageServiceImpl implements PageService {
@Override @Override
public FormBuilder formBuilder(final PageContext pageContext, final int rows) { public FormBuilder formBuilder(final PageContext pageContext, final int rows) {
return new FormBuilder(this, pageContext, rows); return new FormBuilder(this, pageContext, cryptor, rows);
} }
@Override @Override

View file

@ -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();
}
}

View file

@ -1,203 +1,143 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.client; package ch.ethz.seb.sebserver.webservice.servicelayer.client;
import java.io.UnsupportedEncodingException; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import java.nio.CharBuffer; import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import java.security.SecureRandom; import ch.ethz.seb.sebserver.gbl.util.Result;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger;
import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Lazy; import org.springframework.core.env.Environment;
import org.springframework.core.env.Environment; import org.springframework.stereotype.Service;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.security.crypto.keygen.KeyGenerators; import java.io.UnsupportedEncodingException;
import org.springframework.stereotype.Service; import java.nio.CharBuffer;
import java.security.SecureRandom;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; @Lazy
@Service
@Lazy @WebServiceProfile
@Service public class ClientCredentialServiceImpl implements ClientCredentialService {
@WebServiceProfile
public class ClientCredentialServiceImpl implements ClientCredentialService { private static final Logger log = LoggerFactory.getLogger(ClientCredentialServiceImpl.class);
private static final Logger log = LoggerFactory.getLogger(ClientCredentialServiceImpl.class); private final Environment environment;
private final Cryptor cryptor;
static final String SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY = "sebserver.webservice.internalSecret";
protected ClientCredentialServiceImpl(
private final Environment environment; final Environment environment,
final Cryptor cryptor) {
protected ClientCredentialServiceImpl(final Environment environment) {
this.environment = environment; this.environment = environment;
} this.cryptor = cryptor;
}
@Override
public Result<ClientCredentials> generatedClientCredentials() { @Override
return Result.tryCatch(() -> { public Result<ClientCredentials> generatedClientCredentials() {
try { return Result.tryCatch(() -> {
try {
return encryptClientCredentials(
generateClientId(), return encryptClientCredentials(
generateClientSecret()); generateClientId(),
generateClientSecret());
} catch (final UnsupportedEncodingException e) {
log.error("Error while trying to generate client credentials: ", e); } catch (final UnsupportedEncodingException e) {
throw new RuntimeException("cause: ", e); log.error("Error while trying to generate client credentials: ", e);
} throw new RuntimeException("cause: ", e);
}); }
} });
}
@Override
public ClientCredentials encryptClientCredentials( @Override
final CharSequence clientIdPlaintext, public ClientCredentials encryptClientCredentials(
final CharSequence secretPlaintext, final CharSequence clientIdPlaintext,
final CharSequence accessTokenPlaintext) { final CharSequence secretPlaintext,
final CharSequence accessTokenPlaintext) {
final CharSequence secret = this.environment
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY); final CharSequence secret = this.environment
.getProperty(Cryptor.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
return new ClientCredentials(
clientIdPlaintext, return new ClientCredentials(
(StringUtils.isNoneBlank(secretPlaintext)) clientIdPlaintext,
? encrypt(secretPlaintext, secret).toString() (StringUtils.isNoneBlank(secretPlaintext))
: null, ? Cryptor.encrypt(secretPlaintext, secret).toString()
(StringUtils.isNoneBlank(accessTokenPlaintext)) : null,
? encrypt(accessTokenPlaintext, secret).toString() (StringUtils.isNoneBlank(accessTokenPlaintext))
: null); ? Cryptor.encrypt(accessTokenPlaintext, secret).toString()
} : null);
}
@Override
public CharSequence getPlainClientSecret(final ClientCredentials credentials) { @Override
if (credentials == null || !credentials.hasSecret()) { public CharSequence getPlainClientSecret(final ClientCredentials credentials) {
return null; if (credentials == null || !credentials.hasSecret()) {
} return null;
}
final CharSequence secret = this.environment
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY); final CharSequence secret = this.environment
return this.decrypt(credentials.secret, secret); .getProperty(Cryptor.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY);
} return Cryptor.decrypt(credentials.secret, secret);
}
@Override
public CharSequence getPlainAccessToken(final ClientCredentials credentials) { @Override
if (credentials == null || !credentials.hasAccessToken()) { public CharSequence getPlainAccessToken(final ClientCredentials credentials) {
return null; if (credentials == null || !credentials.hasAccessToken()) {
} return null;
}
final CharSequence secret = this.environment
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY); final CharSequence secret = this.environment
.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) { @Override
public CharSequence encrypt(final CharSequence text) {
final CharSequence secret = this.environment return cryptor.encrypt(text);
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY); }
return encrypt(text, secret); @Override
} public CharSequence decrypt(final CharSequence text) {
return cryptor.decrypt(text);
@Override }
public CharSequence decrypt(final CharSequence text) {
private final static char[] possibleCharacters =
final CharSequence secret = this.environment "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^*()-_=+[{]}?"
.getProperty(SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY); .toCharArray();
return decrypt(text, secret); public static CharSequence generateClientId() {
} return RandomStringUtils.random(
16, 0, possibleCharacters.length - 1, false, false,
CharSequence encrypt(final CharSequence text, final CharSequence secret) { possibleCharacters, new SecureRandom());
if (text == null) { }
throw new IllegalArgumentException("Text has null reference");
} 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
if (secret == null) { return RandomStringUtils.random(
log.warn("No internal secret supplied: skip encryption"); 64, 0, possibleCharacters.length - 1, false, false,
return text; possibleCharacters, new SecureRandom());
} }
try { public static void clearChars(final CharSequence sequence) {
if (sequence == null) {
final CharSequence salt = KeyGenerators.string().generateKey(); return;
final CharSequence cipher = Encryptors }
.delux(secret, salt)
.encrypt(text.toString()); if (sequence instanceof CharBuffer) {
((CharBuffer) sequence).clear();
return new StringBuilder(cipher) return;
.append(salt); }
} catch (final Exception e) { throw new IllegalArgumentException(
log.error("Failed to encrypt text: ", e); "Cannot clear chars on CharSequence of type: " + sequence.getClass().getName());
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;
}
}
private final static char[] possibleCharacters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^*()-_=+[{]}?"
.toCharArray();
public final 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
return RandomStringUtils.random(
64, 0, possibleCharacters.length - 1, false, false,
possibleCharacters, new SecureRandom());
}
public final static void clearChars(final CharSequence sequence) {
if (sequence == null) {
return;
}
if (sequence instanceof CharBuffer) {
((CharBuffer) sequence).clear();
return;
}
throw new IllegalArgumentException(
"Cannot clear chars on CharSequence of type: " + sequence.getClass().getName());
}
}

View file

@ -1 +0,0 @@
/ExamDAO.java

View file

@ -1,70 +1,70 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.dao; package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import java.util.Collection; import java.util.Collection;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap; import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
public interface ExamConfigurationMapDAO extends public interface ExamConfigurationMapDAO extends
EntityDAO<ExamConfigurationMap, ExamConfigurationMap>, EntityDAO<ExamConfigurationMap, ExamConfigurationMap>,
BulkActionSupportDAO<ExamConfigurationMap> { BulkActionSupportDAO<ExamConfigurationMap> {
/** Get a specific ExamConfigurationMap by the mapping identifiers /** Get a specific ExamConfigurationMap by the mapping identifiers
* *
* @param examId The Exam mapping identifier * @param examId The Exam mapping identifier
* @param configurationNodeId the ConfigurationNode mapping identifier * @param configurationNodeId the ConfigurationNode mapping identifier
* @return Result refer to the ExamConfigurationMap with specified mapping or to an exception if happened */ * @return Result refer to the ExamConfigurationMap with specified mapping or to an exception if happened */
Result<ExamConfigurationMap> byMapping(Long examId, Long configurationNodeId); Result<ExamConfigurationMap> byMapping(Long examId, Long configurationNodeId);
/** Get the password cipher of a specific ExamConfigurationMap by the mapping identifiers /** Get the password cipher of a specific ExamConfigurationMap by the mapping identifiers
* *
* @param examId The Exam mapping identifier * @param examId The Exam mapping identifier
* @param configurationNodeId the ConfigurationNode mapping identifier * @param configurationNodeId the ConfigurationNode mapping identifier
* @return Result refer to the password cipher of specified mapping or to an exception if happened */ * @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 /** Get the ConfigurationNode identifier of the default Exam Configuration of
* the Exam with specified identifier. * the Exam with specified identifier.
* *
* @param examId The Exam identifier * @param examId The Exam identifier
* @return ConfigurationNode identifier of the default Exam Configuration of * @return ConfigurationNode identifier of the default Exam Configuration of
* the Exam with specified identifier */ * the Exam with specified identifier */
Result<Long> getDefaultConfigurationNode(Long examId); Result<Long> getDefaultConfigurationNode(Long examId);
/** Get the ConfigurationNode identifier of the Exam Configuration of /** Get the ConfigurationNode identifier of the Exam Configuration of
* the Exam for a specified user identifier. * the Exam for a specified user identifier.
* *
* @param examId The Exam identifier * @param examId The Exam identifier
* @param userId the user identifier * @param userId the user identifier
* @return ConfigurationNode identifier of the Exam Configuration of * @return ConfigurationNode identifier of the Exam Configuration of
* the Exam for a specified user identifier */ * the Exam for a specified user identifier */
Result<Long> getUserConfigurationNodeId(final Long examId, final String userId); Result<Long> getUserConfigurationNodeId(final Long examId, final String userId);
/** Get a list of all ConfigurationNode identifiers of configurations that currently are attached to a given Exam /** Get a list of all ConfigurationNode identifiers of configurations that currently are attached to a given Exam
* *
* @param examId the Exam identifier * @param examId the Exam identifier
* @return Result refers to a list of ConfigurationNode identifiers or refer to an error if happened */ * @return Result refers to a list of ConfigurationNode identifiers or refer to an error if happened */
Result<Collection<Long>> getConfigurationNodeIds(Long examId); Result<Collection<Long>> getConfigurationNodeIds(Long examId);
/** Get all id of Exams that has a relation to the given configuration id. /** Get all id of Exams that has a relation to the given configuration id.
* *
* @param configurationNodeId the configuration node identifier (PK) * @param configurationNodeId the configuration node identifier (PK)
* @return Result referencing the List of exam identifiers (PK) for a given configuration node identifier */ * @return Result referencing the List of exam identifiers (PK) for a given configuration node identifier */
Result<Collection<Long>> getExamIdsForConfigNodeId(Long configurationNodeId); Result<Collection<Long>> getExamIdsForConfigNodeId(Long configurationNodeId);
/** Get all id of Exams that has a relation to the given configuration id. /** Get all id of Exams that has a relation to the given configuration id.
* *
* @param configurationId * @param configurationId
* @return Result referencing the List of exam identifiers (PK) for a given configuration identifier */ * @return Result referencing the List of exam identifiers (PK) for a given configuration identifier */
Result<Collection<Long>> getExamIdsForConfigId(Long configurationId); Result<Collection<Long>> getExamIdsForConfigId(Long configurationId);
} }

View file

@ -1,63 +1,63 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.dao; package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionSupportDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
/** Concrete EntityDAO interface of SebClientConfig entities */ /** Concrete EntityDAO interface of SebClientConfig entities */
public interface SebClientConfigDAO extends public interface SebClientConfigDAO extends
ActivatableEntityDAO<SebClientConfig, SebClientConfig>, ActivatableEntityDAO<SebClientConfig, SebClientConfig>,
BulkActionSupportDAO<SebClientConfig> { BulkActionSupportDAO<SebClientConfig> {
/** Get a SebClientConfig by specified client identifier /** Get a SebClientConfig by specified client identifier
* *
* @param clientName the client name * @param clientName the client name
* @return Result refer to the SebClientConfig for client or refer to an error if happened */ * @return Result refer to the SebClientConfig for client or refer to an error if happened */
Result<SebClientConfig> byClientName(String clientName); Result<SebClientConfig> byClientName(String clientName);
/** Get the configured ClientCredentials for a given SebClientConfig. /** Get the configured ClientCredentials for a given SebClientConfig.
* The ClientCredentials are still encoded as they are on DB storage * The ClientCredentials are still encoded as they are on DB storage
* *
* @param modelId the model identifier of the SebClientConfig to get the ClientCredentials for * @param modelId the model identifier of the SebClientConfig to get the ClientCredentials for
* @return the configured ClientCredentials for a given SebClientConfig */ * @return the configured ClientCredentials for a given SebClientConfig */
Result<ClientCredentials> getSebClientCredentials(String modelId); Result<ClientCredentials> getSebClientCredentials(String modelId);
/** Get the stored encrypted configuration password from a specified SEB client configuration. /** 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 * The SEB client configuration password is used to encrypt a SEB Client Configuration
* *
* @param modelId the model * @param modelId the model
* @return encrypted configuration password */ * @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. /** 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. * The SEB client configuration password is used to encrypt a SEB Client Configuration.
* *
* The SEB client configuration must be active otherwise a error is returned * The SEB client configuration must be active otherwise a error is returned
* *
* @param clientName the client name * @param clientName the client name
* @return encrypted configuration password */ * @return encrypted configuration password */
Result<CharSequence> getConfigPasswortCipherByClientName(String clientName); Result<CharSequence> getConfigPasswordCipherByClientName(String clientName);
@Override @Override
@CacheEvict( @CacheEvict(
cacheNames = ClientConfigService.EXAM_CLIENT_DETAILS_CACHE, cacheNames = ClientConfigService.EXAM_CLIENT_DETAILS_CACHE,
allEntries = true) allEntries = true)
Result<Collection<EntityKey>> delete(Set<EntityKey> all); Result<Collection<EntityKey>> delete(Set<EntityKey> all);
} }

View file

@ -1,266 +1,264 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualToWhenPresent; import static org.mybatis.dynamic.sql.SqlBuilder.isEqualToWhenPresent;
import static org.mybatis.dynamic.sql.SqlBuilder.isIn; import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.mybatis.dynamic.sql.SqlBuilder; import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
import io.micrometer.core.instrument.util.StringUtils; import io.micrometer.core.instrument.util.StringUtils;
@Lazy @Lazy
@Component @Component
@WebServiceProfile @WebServiceProfile
public class ClientConnectionDAOImpl implements ClientConnectionDAO { public class ClientConnectionDAOImpl implements ClientConnectionDAO {
private final ClientConnectionRecordMapper clientConnectionRecordMapper; private final ClientConnectionRecordMapper clientConnectionRecordMapper;
private final ClientEventRecordMapper clientEventRecordMapper; private final ClientEventRecordMapper clientEventRecordMapper;
protected ClientConnectionDAOImpl( protected ClientConnectionDAOImpl(
final ClientConnectionRecordMapper clientConnectionRecordMapper, final ClientConnectionRecordMapper clientConnectionRecordMapper,
final ClientEventRecordMapper clientEventRecordMapper) { final ClientEventRecordMapper clientEventRecordMapper) {
this.clientConnectionRecordMapper = clientConnectionRecordMapper; this.clientConnectionRecordMapper = clientConnectionRecordMapper;
this.clientEventRecordMapper = clientEventRecordMapper; this.clientEventRecordMapper = clientEventRecordMapper;
} }
@Override @Override
public EntityType entityType() { public EntityType entityType() {
return EntityType.CLIENT_CONNECTION; return EntityType.CLIENT_CONNECTION;
} }
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Result<ClientConnection> byPK(final Long id) { public Result<ClientConnection> byPK(final Long id) {
return recordById(id) return recordById(id)
.flatMap(ClientConnectionDAOImpl::toDomainModel); .flatMap(ClientConnectionDAOImpl::toDomainModel);
} }
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Result<Collection<ClientConnection>> allMatching( public Result<Collection<ClientConnection>> allMatching(
final FilterMap filterMap, final FilterMap filterMap,
final Predicate<ClientConnection> predicate) { final Predicate<ClientConnection> predicate) {
return Result.tryCatch(() -> this.clientConnectionRecordMapper return Result.tryCatch(() -> this.clientConnectionRecordMapper
.selectByExample() .selectByExample()
.where( .where(
ClientConnectionRecordDynamicSqlSupport.institutionId, ClientConnectionRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(filterMap.getInstitutionId())) isEqualToWhenPresent(filterMap.getInstitutionId()))
.and( .and(
ClientConnectionRecordDynamicSqlSupport.examId, ClientConnectionRecordDynamicSqlSupport.examId,
isEqualToWhenPresent(filterMap.getClientConnectionExamId())) isEqualToWhenPresent(filterMap.getClientConnectionExamId()))
.and( .and(
ClientConnectionRecordDynamicSqlSupport.status, ClientConnectionRecordDynamicSqlSupport.status,
isEqualToWhenPresent(filterMap.getClientConnectionStatus())) isEqualToWhenPresent(filterMap.getClientConnectionStatus()))
.build() .build()
.execute() .execute()
.stream() .stream()
.map(ClientConnectionDAOImpl::toDomainModel) .map(ClientConnectionDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError) .flatMap(DAOLoggingSupport::logAndSkipOnError)
.filter(predicate) .filter(predicate)
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Result<Collection<ClientConnection>> allOf(final Set<Long> pks) { public Result<Collection<ClientConnection>> allOf(final Set<Long> pks) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> this.clientConnectionRecordMapper.selectByExample()
return this.clientConnectionRecordMapper.selectByExample() .where(ClientConnectionRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.where(ClientConnectionRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks))) .build()
.build() .execute()
.execute() .stream()
.stream() .map(ClientConnectionDAOImpl::toDomainModel)
.map(ClientConnectionDAOImpl::toDomainModel) .flatMap(DAOLoggingSupport::logAndSkipOnError)
.flatMap(DAOLoggingSupport::logAndSkipOnError) .collect(Collectors.toList()));
.collect(Collectors.toList()); }
});
} @Override
@Transactional(readOnly = true)
@Override public Result<Collection<String>> getConnectionTokens(final Long examId) {
@Transactional(readOnly = true) return Result.tryCatch(() -> {
public Result<Collection<String>> getConnectionTokens(final Long examId) { return this.clientConnectionRecordMapper
return Result.tryCatch(() -> { .selectByExample()
return this.clientConnectionRecordMapper .where(
.selectByExample() ClientConnectionRecordDynamicSqlSupport.examId,
.where( SqlBuilder.isEqualTo(examId))
ClientConnectionRecordDynamicSqlSupport.examId, .build()
SqlBuilder.isEqualTo(examId)) .execute()
.build() .stream()
.execute() .map(ClientConnectionRecord::getConnectionToken)
.stream() .filter(StringUtils::isNotBlank)
.map(ClientConnectionRecord::getConnectionToken) .collect(Collectors.toList());
.filter(StringUtils::isNotBlank) });
.collect(Collectors.toList()); }
});
} @Override
@Transactional
@Override public Result<ClientConnection> createNew(final ClientConnection data) {
@Transactional return Result.tryCatch(() -> {
public Result<ClientConnection> createNew(final ClientConnection data) {
return Result.tryCatch(() -> { final ClientConnectionRecord newRecord = new ClientConnectionRecord(
null,
final ClientConnectionRecord newRecord = new ClientConnectionRecord( data.institutionId,
null, data.examId,
data.institutionId, ConnectionStatus.CONNECTION_REQUESTED.name(),
data.examId, data.connectionToken,
ConnectionStatus.CONNECTION_REQUESTED.name(), null,
data.connectionToken, data.clientAddress,
null, data.virtualClientAddress,
data.clientAddress, Utils.getMillisecondsNow());
data.virtualClientAddress,
Utils.getMillisecondsNow()); this.clientConnectionRecordMapper.insert(newRecord);
return newRecord;
this.clientConnectionRecordMapper.insert(newRecord); })
return newRecord; .flatMap(ClientConnectionDAOImpl::toDomainModel)
}) .onError(TransactionHandler::rollback);
.flatMap(ClientConnectionDAOImpl::toDomainModel) }
.onError(TransactionHandler::rollback);
} @Override
@Transactional
@Override public Result<ClientConnection> save(final ClientConnection data) {
@Transactional return Result.tryCatch(() -> {
public Result<ClientConnection> save(final ClientConnection data) {
return Result.tryCatch(() -> { final ClientConnectionRecord updateRecord = new ClientConnectionRecord(
data.id,
final ClientConnectionRecord updateRecord = new ClientConnectionRecord( null,
data.id, data.examId,
null, data.status != null ? data.status.name() : null,
data.examId, null,
data.status != null ? data.status.name() : null, data.userSessionId,
null, data.clientAddress,
data.userSessionId, data.virtualClientAddress,
data.clientAddress, null);
data.virtualClientAddress,
null); this.clientConnectionRecordMapper.updateByPrimaryKeySelective(updateRecord);
return this.clientConnectionRecordMapper.selectByPrimaryKey(data.id);
this.clientConnectionRecordMapper.updateByPrimaryKeySelective(updateRecord); })
return this.clientConnectionRecordMapper.selectByPrimaryKey(data.id); .flatMap(ClientConnectionDAOImpl::toDomainModel)
}) .onError(TransactionHandler::rollback);
.flatMap(ClientConnectionDAOImpl::toDomainModel) }
.onError(TransactionHandler::rollback);
} @Override
@Transactional
@Override public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
@Transactional return Result.tryCatch(() -> {
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
return Result.tryCatch(() -> { final List<Long> ids = extractListOfPKs(all);
final List<Long> ids = extractListOfPKs(all); // first delete all related client events
this.clientEventRecordMapper.deleteByExample()
// first delete all related client events .where(
this.clientEventRecordMapper.deleteByExample() ClientEventRecordDynamicSqlSupport.clientConnectionId,
.where( SqlBuilder.isIn(ids))
ClientEventRecordDynamicSqlSupport.clientConnectionId, .build()
SqlBuilder.isIn(ids)) .execute();
.build()
.execute(); // then delete all requested client-connections
this.clientConnectionRecordMapper.deleteByExample()
// then delete all requested client-connections .where(
this.clientConnectionRecordMapper.deleteByExample() ClientConnectionRecordDynamicSqlSupport.id,
.where( SqlBuilder.isIn(ids))
ClientConnectionRecordDynamicSqlSupport.id, .build()
SqlBuilder.isIn(ids)) .execute();
.build()
.execute(); return ids.stream()
.map(id -> new EntityKey(id, EntityType.CLIENT_CONNECTION))
return ids.stream() .collect(Collectors.toList());
.map(id -> new EntityKey(id, EntityType.CLIENT_CONNECTION)) });
.collect(Collectors.toList()); }
});
} @Override
public Result<ClientConnection> byConnectionToken(final String connectionToken) {
@Override return Result.tryCatch(() -> {
public Result<ClientConnection> byConnectionToken(final String connectionToken) { final List<ClientConnectionRecord> list = this.clientConnectionRecordMapper
return Result.tryCatch(() -> { .selectByExample()
final List<ClientConnectionRecord> list = this.clientConnectionRecordMapper .where(
.selectByExample() ClientConnectionRecordDynamicSqlSupport.connectionToken,
.where( SqlBuilder.isEqualTo(connectionToken))
ClientConnectionRecordDynamicSqlSupport.connectionToken,
SqlBuilder.isEqualTo(connectionToken)) .build()
.execute();
.build()
.execute(); if (list.isEmpty()) {
throw new ResourceNotFoundException(EntityType.CLIENT_CONNECTION, "connectionToken");
if (list.isEmpty()) { }
throw new ResourceNotFoundException(EntityType.CLIENT_CONNECTION, "connectionToken");
} if (list.size() > 1) {
throw new IllegalStateException("Only one ClientConnection expected but there are: " + list.size());
if (list.size() > 1) { }
throw new IllegalStateException("Only one ClientConnection expected but there are: " + list.size());
} return list.get(0);
})
return list.get(0); .flatMap(ClientConnectionDAOImpl::toDomainModel);
}) }
.flatMap(ClientConnectionDAOImpl::toDomainModel);
} private Result<ClientConnectionRecord> recordById(final Long id) {
return Result.tryCatch(() -> {
private Result<ClientConnectionRecord> recordById(final Long id) {
return Result.tryCatch(() -> { final ClientConnectionRecord record = this.clientConnectionRecordMapper.selectByPrimaryKey(id);
if (record == null) {
final ClientConnectionRecord record = this.clientConnectionRecordMapper.selectByPrimaryKey(id); throw new ResourceNotFoundException(
if (record == null) { entityType(),
throw new ResourceNotFoundException( String.valueOf(id));
entityType(), }
String.valueOf(id));
} return record;
});
return record; }
});
} private static Result<ClientConnection> toDomainModel(final ClientConnectionRecord record) {
return Result.tryCatch(() -> {
private static Result<ClientConnection> toDomainModel(final ClientConnectionRecord record) {
return Result.tryCatch(() -> { final String status = record.getStatus();
return new ClientConnection(
final String status = record.getStatus(); record.getId(),
return new ClientConnection( record.getInstitutionId(),
record.getId(), record.getExamId(),
record.getInstitutionId(), (StringUtils.isNotBlank(status))
record.getExamId(), ? ConnectionStatus.valueOf(status)
(StringUtils.isNotBlank(status)) : ConnectionStatus.UNDEFINED,
? ConnectionStatus.valueOf(status) record.getConnectionToken(),
: ConnectionStatus.UNDEFINED, record.getExamUserSessionId(),
record.getConnectionToken(), record.getClientAddress(),
record.getExamUserSessionId(), record.getVirtualClientAddress(),
record.getClientAddress(), record.getCreationTime());
record.getVirtualClientAddress(), });
record.getCreationTime());
}); }
} }
}

View file

@ -1,265 +1,257 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualToWhenPresent; import static org.mybatis.dynamic.sql.SqlBuilder.isEqualToWhenPresent;
import static org.mybatis.dynamic.sql.SqlBuilder.isIn; import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.mybatis.dynamic.sql.SqlBuilder; import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventExtentionMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventExtentionMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventExtentionMapper.ConnectionEventJoinRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.ClientEventExtentionMapper.ConnectionEventJoinRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientConnectionRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientEventRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientEventRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientEventDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@Lazy @Lazy
@Component @Component
@WebServiceProfile @WebServiceProfile
public class ClientEventDAOImpl implements ClientEventDAO { public class ClientEventDAOImpl implements ClientEventDAO {
private final ClientEventRecordMapper clientEventRecordMapper; private final ClientEventRecordMapper clientEventRecordMapper;
private final ClientEventExtentionMapper clientEventExtentionMapper; private final ClientEventExtentionMapper clientEventExtentionMapper;
protected ClientEventDAOImpl( protected ClientEventDAOImpl(
final ClientEventRecordMapper clientEventRecordMapper, final ClientEventRecordMapper clientEventRecordMapper,
final ClientEventExtentionMapper clientEventExtentionMapper) { final ClientEventExtentionMapper clientEventExtentionMapper) {
this.clientEventRecordMapper = clientEventRecordMapper; this.clientEventRecordMapper = clientEventRecordMapper;
this.clientEventExtentionMapper = clientEventExtentionMapper; this.clientEventExtentionMapper = clientEventExtentionMapper;
} }
@Override @Override
public EntityType entityType() { public EntityType entityType() {
return EntityType.CLIENT_EVENT; return EntityType.CLIENT_EVENT;
} }
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Result<ClientEvent> byPK(final Long id) { public Result<ClientEvent> byPK(final Long id) {
return recordById(id) return recordById(id)
.flatMap(ClientEventDAOImpl::toDomainModel); .flatMap(ClientEventDAOImpl::toDomainModel);
} }
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Result<Collection<ClientEvent>> allMatching( public Result<Collection<ClientEvent>> allMatching(
final FilterMap filterMap, final FilterMap filterMap,
final Predicate<ClientEvent> predicate) { final Predicate<ClientEvent> predicate) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> this.clientEventRecordMapper
.selectByExample()
return this.clientEventRecordMapper .where(
.selectByExample() ClientEventRecordDynamicSqlSupport.clientConnectionId,
.where( isEqualToWhenPresent(filterMap.getClientEventConnectionId()))
ClientEventRecordDynamicSqlSupport.clientConnectionId, .and(
isEqualToWhenPresent(filterMap.getClientEventConnectionId())) ClientEventRecordDynamicSqlSupport.type,
.and( isEqualToWhenPresent(filterMap.getClientEventTypeId()))
ClientEventRecordDynamicSqlSupport.type, .and(
isEqualToWhenPresent(filterMap.getClientEventTypeId())) ClientEventRecordDynamicSqlSupport.type,
.and( SqlBuilder.isNotEqualTo(EventType.LAST_PING.id))
ClientEventRecordDynamicSqlSupport.type, .and(
SqlBuilder.isNotEqualTo(EventType.LAST_PING.id)) ClientEventRecordDynamicSqlSupport.clientTime,
.and( SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeFrom()))
ClientEventRecordDynamicSqlSupport.clientTime, .and(
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeFrom())) ClientEventRecordDynamicSqlSupport.clientTime,
.and( SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeTo()))
ClientEventRecordDynamicSqlSupport.clientTime, .and(
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeTo())) ClientEventRecordDynamicSqlSupport.serverTime,
.and( SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeFrom()))
ClientEventRecordDynamicSqlSupport.serverTime, .and(
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeFrom())) ClientEventRecordDynamicSqlSupport.serverTime,
.and( SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeTo()))
ClientEventRecordDynamicSqlSupport.serverTime, .and(
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeTo())) ClientEventRecordDynamicSqlSupport.text,
.and( SqlBuilder.isLikeWhenPresent(filterMap.getClientEventText()))
ClientEventRecordDynamicSqlSupport.text, .build()
SqlBuilder.isLikeWhenPresent(filterMap.getClientEventText())) .execute()
.build() .stream()
.execute() .map(ClientEventDAOImpl::toDomainModel)
.stream() .flatMap(DAOLoggingSupport::logAndSkipOnError)
.map(ClientEventDAOImpl::toDomainModel) .filter(predicate)
.flatMap(DAOLoggingSupport::logAndSkipOnError) .collect(Collectors.toList()));
.filter(predicate) }
.collect(Collectors.toList());
}); @Override
} public Result<Collection<ExtendedClientEvent>> allMatchingExtended(
final FilterMap filterMap,
@Override final Predicate<ExtendedClientEvent> predicate) {
public Result<Collection<ExtendedClientEvent>> allMatchingExtended(
final FilterMap filterMap, return Result.tryCatch(() -> this.clientEventExtentionMapper.selectByExample()
final Predicate<ExtendedClientEvent> predicate) { .where(
ClientConnectionRecordDynamicSqlSupport.institutionId,
return Result.tryCatch(() -> this.clientEventExtentionMapper.selectByExample() isEqualToWhenPresent(filterMap.getInstitutionId()))
.where( .and(
ClientConnectionRecordDynamicSqlSupport.institutionId, ClientConnectionRecordDynamicSqlSupport.examId,
isEqualToWhenPresent(filterMap.getInstitutionId())) isEqualToWhenPresent(filterMap.getClientEventExamId()))
.and( .and(
ClientConnectionRecordDynamicSqlSupport.examId, ClientConnectionRecordDynamicSqlSupport.examUserSessionId,
isEqualToWhenPresent(filterMap.getClientEventExamId())) SqlBuilder.isLikeWhenPresent(filterMap.getSQLWildcard(ClientConnection.FILTER_ATTR_SESSION_ID)))
.and( .and(
ClientConnectionRecordDynamicSqlSupport.examUserSessionId, ClientEventRecordDynamicSqlSupport.clientConnectionId,
SqlBuilder.isLikeWhenPresent(filterMap.getSQLWildcard(ClientConnection.FILTER_ATTR_SESSION_ID))) isEqualToWhenPresent(filterMap.getClientEventConnectionId()))
.and( .and(
ClientEventRecordDynamicSqlSupport.clientConnectionId, ClientEventRecordDynamicSqlSupport.type,
isEqualToWhenPresent(filterMap.getClientEventConnectionId())) isEqualToWhenPresent(filterMap.getClientEventTypeId()))
.and( .and(
ClientEventRecordDynamicSqlSupport.type, ClientEventRecordDynamicSqlSupport.type,
isEqualToWhenPresent(filterMap.getClientEventTypeId())) SqlBuilder.isNotEqualTo(EventType.LAST_PING.id))
.and( .and(
ClientEventRecordDynamicSqlSupport.type, ClientEventRecordDynamicSqlSupport.clientTime,
SqlBuilder.isNotEqualTo(EventType.LAST_PING.id)) SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeFrom()))
.and( .and(
ClientEventRecordDynamicSqlSupport.clientTime, ClientEventRecordDynamicSqlSupport.clientTime,
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeFrom())) SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeTo()))
.and( .and(
ClientEventRecordDynamicSqlSupport.clientTime, ClientEventRecordDynamicSqlSupport.serverTime,
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventClientTimeTo())) SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeFrom()))
.and( .and(
ClientEventRecordDynamicSqlSupport.serverTime, ClientEventRecordDynamicSqlSupport.serverTime,
SqlBuilder.isGreaterThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeFrom())) SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeTo()))
.and( .and(
ClientEventRecordDynamicSqlSupport.serverTime, ClientEventRecordDynamicSqlSupport.text,
SqlBuilder.isLessThanOrEqualToWhenPresent(filterMap.getClientEventServerTimeTo())) SqlBuilder.isLikeWhenPresent(filterMap.getClientEventText()))
.and( .build()
ClientEventRecordDynamicSqlSupport.text, .execute()
SqlBuilder.isLikeWhenPresent(filterMap.getClientEventText())) .stream()
.build() .map(ClientEventDAOImpl::toDomainModelExtended)
.execute() .flatMap(DAOLoggingSupport::logAndSkipOnError)
.stream() .filter(predicate)
.map(ClientEventDAOImpl::toDomainModelExtended) .collect(Collectors.toList()));
.flatMap(DAOLoggingSupport::logAndSkipOnError) }
.filter(predicate)
.collect(Collectors.toList())); @Override
} @Transactional(readOnly = true)
public Result<Collection<ClientEvent>> allOf(final Set<Long> pks) {
@Override return Result.tryCatch(() -> this.clientEventRecordMapper.selectByExample()
@Transactional(readOnly = true) .where(ClientEventRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
public Result<Collection<ClientEvent>> allOf(final Set<Long> pks) { .build()
return Result.tryCatch(() -> { .execute()
return this.clientEventRecordMapper.selectByExample() .stream()
.where(ClientEventRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks))) .map(ClientEventDAOImpl::toDomainModel)
.build() .flatMap(DAOLoggingSupport::logAndSkipOnError)
.execute() .collect(Collectors.toList()));
.stream() }
.map(ClientEventDAOImpl::toDomainModel)
.flatMap(DAOLoggingSupport::logAndSkipOnError) @Override
.collect(Collectors.toList()); @Transactional
}); public Result<ClientEvent> createNew(final ClientEvent data) {
} return Result.tryCatch(() -> {
@Override final EventType eventType = data.getEventType();
@Transactional
public Result<ClientEvent> createNew(final ClientEvent data) { final ClientEventRecord newRecord = new ClientEventRecord(
return Result.tryCatch(() -> { null,
data.connectionId,
final EventType eventType = data.getEventType(); (eventType != null) ? eventType.id : EventType.UNKNOWN.id,
data.clientTime,
final ClientEventRecord newRecord = new ClientEventRecord( data.serverTime,
null, (data.numValue != null) ? new BigDecimal(data.numValue) : null,
data.connectionId, data.text);
(eventType != null) ? eventType.id : EventType.UNKNOWN.id,
data.clientTime, this.clientEventRecordMapper.insertSelective(newRecord);
data.serverTime, return newRecord;
(data.numValue != null) ? new BigDecimal(data.numValue) : null, })
data.text); .flatMap(ClientEventDAOImpl::toDomainModel)
.onError(TransactionHandler::rollback);
this.clientEventRecordMapper.insertSelective(newRecord); }
return newRecord;
}) @Override
.flatMap(ClientEventDAOImpl::toDomainModel) @Transactional
.onError(TransactionHandler::rollback); public Result<ClientEvent> save(final ClientEvent data) {
} throw new UnsupportedOperationException("Update is not supported for client events");
}
@Override
@Transactional @Override
public Result<ClientEvent> save(final ClientEvent data) { @Transactional
throw new UnsupportedOperationException("Update is not supported for client events"); public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
} throw new UnsupportedOperationException(
"Delete is not supported for particular client events. "
@Override + "Use delete of a client connection to delete also all client events of this connection.");
@Transactional }
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
throw new UnsupportedOperationException( private Result<ClientEventRecord> recordById(final Long id) {
"Delete is not supported for particular client events. " return Result.tryCatch(() -> {
+ "Use delete of a client connection to delete also all client events of this connection.");
} final ClientEventRecord record = this.clientEventRecordMapper.selectByPrimaryKey(id);
if (record == null) {
private Result<ClientEventRecord> recordById(final Long id) { throw new ResourceNotFoundException(
return Result.tryCatch(() -> { entityType(),
String.valueOf(id));
final ClientEventRecord record = this.clientEventRecordMapper.selectByPrimaryKey(id); }
if (record == null) {
throw new ResourceNotFoundException( return record;
entityType(), });
String.valueOf(id)); }
}
private static Result<ClientEvent> toDomainModel(final ClientEventRecord record) {
return record; return Result.tryCatch(() -> {
});
} final Integer type = record.getType();
final BigDecimal numericValue = record.getNumericValue();
private static Result<ClientEvent> toDomainModel(final ClientEventRecord record) { return new ClientEvent(
return Result.tryCatch(() -> { record.getId(),
record.getClientConnectionId(),
final Integer type = record.getType(); (type != null) ? EventType.byId(type) : EventType.UNKNOWN,
final BigDecimal numericValue = record.getNumericValue(); record.getClientTime(),
return new ClientEvent( record.getServerTime(),
record.getId(), (numericValue != null) ? numericValue.doubleValue() : null,
record.getClientConnectionId(), record.getText());
(type != null) ? EventType.byId(type) : EventType.UNKNOWN, });
record.getClientTime(), }
record.getServerTime(),
(numericValue != null) ? numericValue.doubleValue() : null, private static Result<ExtendedClientEvent> toDomainModelExtended(final ConnectionEventJoinRecord record) {
record.getText()); return Result.tryCatch(() -> new ExtendedClientEvent(
}); record.institution_id,
} record.exam_id,
record.exam_user_session_identifer,
private static Result<ExtendedClientEvent> toDomainModelExtended(final ConnectionEventJoinRecord record) { record.id,
return Result.tryCatch(() -> { record.connection_id,
(record.type != null) ? EventType.byId(record.type) : EventType.UNKNOWN,
return new ExtendedClientEvent( record.client_time,
record.institution_id, record.server_time,
record.exam_id, record.numeric_value,
record.exam_user_session_identifer, record.text));
record.id, }
record.connection_id,
(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);
});
}
}

View file

@ -1,266 +1,264 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.isIn; import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.mybatis.dynamic.sql.SqlBuilder; import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; 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.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationAttributeRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationAttributeRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationAttributeRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationAttributeRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValueRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValueRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValueRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationValueRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.OrientationRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationAttributeRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationAttributeRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@Lazy @Lazy
@Component @Component
@WebServiceProfile @WebServiceProfile
public class ConfigurationAttributeDAOImpl implements ConfigurationAttributeDAO { public class ConfigurationAttributeDAOImpl implements ConfigurationAttributeDAO {
private final ConfigurationAttributeRecordMapper configurationAttributeRecordMapper; private final ConfigurationAttributeRecordMapper configurationAttributeRecordMapper;
private final ConfigurationValueRecordMapper configurationValueRecordMapper; private final ConfigurationValueRecordMapper configurationValueRecordMapper;
private final OrientationRecordMapper orientationRecordMapper; private final OrientationRecordMapper orientationRecordMapper;
protected ConfigurationAttributeDAOImpl( protected ConfigurationAttributeDAOImpl(
final ConfigurationAttributeRecordMapper configurationAttributeRecordMapper, final ConfigurationAttributeRecordMapper configurationAttributeRecordMapper,
final ConfigurationValueRecordMapper configurationValueRecordMapper, final ConfigurationValueRecordMapper configurationValueRecordMapper,
final OrientationRecordMapper orientationRecordMapper) { final OrientationRecordMapper orientationRecordMapper) {
this.configurationAttributeRecordMapper = configurationAttributeRecordMapper; this.configurationAttributeRecordMapper = configurationAttributeRecordMapper;
this.configurationValueRecordMapper = configurationValueRecordMapper; this.configurationValueRecordMapper = configurationValueRecordMapper;
this.orientationRecordMapper = orientationRecordMapper; this.orientationRecordMapper = orientationRecordMapper;
} }
@Override @Override
public EntityType entityType() { public EntityType entityType() {
return EntityType.CONFIGURATION_ATTRIBUTE; return EntityType.CONFIGURATION_ATTRIBUTE;
} }
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Result<ConfigurationAttribute> byPK(final Long id) { public Result<ConfigurationAttribute> byPK(final Long id) {
return recordById(id) return recordById(id)
.flatMap(ConfigurationAttributeDAOImpl::toDomainModel); .flatMap(ConfigurationAttributeDAOImpl::toDomainModel);
} }
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Result<Collection<ConfigurationAttribute>> allOf(final Set<Long> pks) { public Result<Collection<ConfigurationAttribute>> allOf(final Set<Long> pks) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> this.configurationAttributeRecordMapper.selectByExample()
return this.configurationAttributeRecordMapper.selectByExample() .where(ConfigurationAttributeRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.where(ConfigurationAttributeRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks))) .build()
.build() .execute()
.execute() .stream()
.stream() .map(ConfigurationAttributeDAOImpl::toDomainModel)
.map(ConfigurationAttributeDAOImpl::toDomainModel) .flatMap(DAOLoggingSupport::logAndSkipOnError)
.flatMap(DAOLoggingSupport::logAndSkipOnError) .collect(Collectors.toList()));
.collect(Collectors.toList()); }
});
} @Override
@Transactional(readOnly = true)
@Override public Result<Collection<ConfigurationAttribute>> allMatching(
@Transactional(readOnly = true) final FilterMap filterMap,
public Result<Collection<ConfigurationAttribute>> allMatching( final Predicate<ConfigurationAttribute> predicate) {
final FilterMap filterMap,
final Predicate<ConfigurationAttribute> predicate) { return Result.tryCatch(() -> this.configurationAttributeRecordMapper
.selectByExample()
return Result.tryCatch(() -> this.configurationAttributeRecordMapper .where(
.selectByExample() ConfigurationAttributeRecordDynamicSqlSupport.parentId,
.where( SqlBuilder.isEqualToWhenPresent(filterMap.getConfigAttributeParentId()))
ConfigurationAttributeRecordDynamicSqlSupport.parentId, .and(
SqlBuilder.isEqualToWhenPresent(filterMap.getConfigAttributeParentId())) ConfigurationAttributeRecordDynamicSqlSupport.type,
.and( SqlBuilder.isEqualToWhenPresent(filterMap.getConfigAttributeType()))
ConfigurationAttributeRecordDynamicSqlSupport.type, .build()
SqlBuilder.isEqualToWhenPresent(filterMap.getConfigAttributeType())) .execute()
.build() .stream()
.execute() .map(ConfigurationAttributeDAOImpl::toDomainModel)
.stream() .flatMap(DAOLoggingSupport::logAndSkipOnError)
.map(ConfigurationAttributeDAOImpl::toDomainModel) .filter(predicate)
.flatMap(DAOLoggingSupport::logAndSkipOnError) .collect(Collectors.toList()));
.filter(predicate) }
.collect(Collectors.toList()));
} @Override
@Transactional(readOnly = true)
@Override public Result<Collection<ConfigurationAttribute>> allChildAttributes(final Long parentId) {
@Transactional(readOnly = true) return Result.tryCatch(() -> this.configurationAttributeRecordMapper
public Result<Collection<ConfigurationAttribute>> allChildAttributes(final Long parentId) { .selectByExample()
return Result.tryCatch(() -> this.configurationAttributeRecordMapper .where(
.selectByExample() ConfigurationAttributeRecordDynamicSqlSupport.parentId,
.where( SqlBuilder.isEqualTo(parentId))
ConfigurationAttributeRecordDynamicSqlSupport.parentId, .build()
SqlBuilder.isEqualTo(parentId)) .execute()
.build() .stream()
.execute() .map(ConfigurationAttributeDAOImpl::toDomainModel)
.stream() .flatMap(DAOLoggingSupport::logAndSkipOnError)
.map(ConfigurationAttributeDAOImpl::toDomainModel) .collect(Collectors.toList()));
.flatMap(DAOLoggingSupport::logAndSkipOnError) }
.collect(Collectors.toList()));
} @Override
@Transactional(readOnly = true)
@Override public Result<Collection<ConfigurationAttribute>> getAllRootAttributes() {
@Transactional(readOnly = true) return Result.tryCatch(() -> this.configurationAttributeRecordMapper
public Result<Collection<ConfigurationAttribute>> getAllRootAttributes() { .selectByExample()
return Result.tryCatch(() -> this.configurationAttributeRecordMapper .where(
.selectByExample() ConfigurationAttributeRecordDynamicSqlSupport.parentId,
.where( SqlBuilder.isNull())
ConfigurationAttributeRecordDynamicSqlSupport.parentId, .build()
SqlBuilder.isNull()) .execute()
.build() .stream()
.execute() .map(ConfigurationAttributeDAOImpl::toDomainModel)
.stream() .flatMap(DAOLoggingSupport::logAndSkipOnError)
.map(ConfigurationAttributeDAOImpl::toDomainModel) .collect(Collectors.toList()));
.flatMap(DAOLoggingSupport::logAndSkipOnError) }
.collect(Collectors.toList()));
} @Override
@Transactional
@Override public Result<ConfigurationAttribute> createNew(final ConfigurationAttribute data) {
@Transactional return Result.tryCatch(() -> {
public Result<ConfigurationAttribute> createNew(final ConfigurationAttribute data) { final ConfigurationAttributeRecord newRecord = new ConfigurationAttributeRecord(
return Result.tryCatch(() -> { null,
final ConfigurationAttributeRecord newRecord = new ConfigurationAttributeRecord( data.name,
null, data.type.name(),
data.name, data.parentId,
data.type.name(), data.resources,
data.parentId, data.validator,
data.resources, data.dependencies,
data.validator, data.defaultValue);
data.dependencies,
data.defaultValue); this.configurationAttributeRecordMapper.insert(newRecord);
return newRecord;
this.configurationAttributeRecordMapper.insert(newRecord); })
return newRecord; .flatMap(ConfigurationAttributeDAOImpl::toDomainModel)
}) .onError(TransactionHandler::rollback);
.flatMap(ConfigurationAttributeDAOImpl::toDomainModel) }
.onError(TransactionHandler::rollback);
} @Override
@Transactional
@Override public Result<ConfigurationAttribute> save(final ConfigurationAttribute data) {
@Transactional return Result.tryCatch(() -> {
public Result<ConfigurationAttribute> save(final ConfigurationAttribute data) {
return Result.tryCatch(() -> { final ConfigurationAttributeRecord newRecord = new ConfigurationAttributeRecord(
data.id,
final ConfigurationAttributeRecord newRecord = new ConfigurationAttributeRecord( data.name,
data.id, data.type.name(),
data.name, data.parentId,
data.type.name(), data.resources,
data.parentId, data.validator,
data.resources, data.dependencies,
data.validator, data.defaultValue);
data.dependencies,
data.defaultValue); this.configurationAttributeRecordMapper.updateByPrimaryKeySelective(newRecord);
return this.configurationAttributeRecordMapper.selectByPrimaryKey(data.id);
this.configurationAttributeRecordMapper.updateByPrimaryKeySelective(newRecord); })
return this.configurationAttributeRecordMapper.selectByPrimaryKey(data.id); .flatMap(ConfigurationAttributeDAOImpl::toDomainModel)
}) .onError(TransactionHandler::rollback);
.flatMap(ConfigurationAttributeDAOImpl::toDomainModel) }
.onError(TransactionHandler::rollback);
} @Override
@Transactional
@Override public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
@Transactional return Result.tryCatch(() -> {
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
return Result.tryCatch(() -> { final List<Long> ids = extractListOfPKs(all);
final List<EntityKey> result = new ArrayList<>();
final List<Long> ids = extractListOfPKs(all);
final List<EntityKey> result = new ArrayList<>(); // if this is a complex attribute that has children, delete the children first
final List<ConfigurationAttributeRecord> children =
// if this is a complex attribute that has children, delete the children first this.configurationAttributeRecordMapper.selectByExample()
final List<ConfigurationAttributeRecord> children = .where(ConfigurationAttributeRecordDynamicSqlSupport.parentId, isIn(ids))
this.configurationAttributeRecordMapper.selectByExample() .build()
.where(ConfigurationAttributeRecordDynamicSqlSupport.parentId, isIn(ids)) .execute();
.build()
.execute(); // recursive call for children and adding result to the overall result set
if (children != null && !children.isEmpty()) {
// recursive call for children and adding result to the overall result set result.addAll(delete(children.stream()
if (children != null && !children.isEmpty()) { .map(r -> new EntityKey(r.getId(), EntityType.CONFIGURATION_ATTRIBUTE))
result.addAll(delete(children.stream() .collect(Collectors.toSet()))
.map(r -> new EntityKey(r.getId(), EntityType.CONFIGURATION_ATTRIBUTE)) .getOrThrow());
.collect(Collectors.toSet())) }
.getOrThrow());
} // delete all ConfigurationValue's that belongs to the ConfigurationAttributes to delete
this.configurationValueRecordMapper.deleteByExample()
// delete all ConfigurationValue's that belongs to the ConfigurationAttributes to delete .where(
this.configurationValueRecordMapper.deleteByExample() ConfigurationValueRecordDynamicSqlSupport.configurationAttributeId,
.where( SqlBuilder.isIn(ids))
ConfigurationValueRecordDynamicSqlSupport.configurationAttributeId, .build()
SqlBuilder.isIn(ids)) .execute();
.build()
.execute(); // delete all Orientations that belongs to the ConfigurationAttributes to delete
this.orientationRecordMapper.deleteByExample()
// delete all Orientations that belongs to the ConfigurationAttributes to delete .where(
this.orientationRecordMapper.deleteByExample() OrientationRecordDynamicSqlSupport.configAttributeId,
.where( SqlBuilder.isIn(ids))
OrientationRecordDynamicSqlSupport.configAttributeId, .build()
SqlBuilder.isIn(ids)) .execute();
.build()
.execute(); // then delete all requested ConfigurationAttributes
this.configurationAttributeRecordMapper.deleteByExample()
// then delete all requested ConfigurationAttributes .where(ConfigurationAttributeRecordDynamicSqlSupport.id, isIn(ids))
this.configurationAttributeRecordMapper.deleteByExample() .build()
.where(ConfigurationAttributeRecordDynamicSqlSupport.id, isIn(ids)) .execute();
.build()
.execute(); result.addAll(ids.stream()
.map(id -> new EntityKey(id, EntityType.CONFIGURATION_ATTRIBUTE))
result.addAll(ids.stream() .collect(Collectors.toList()));
.map(id -> new EntityKey(id, EntityType.CONFIGURATION_ATTRIBUTE))
.collect(Collectors.toList())); return result;
});
return result; }
});
} Result<ConfigurationAttributeRecord> recordById(final Long id) {
return Result.tryCatch(() -> {
Result<ConfigurationAttributeRecord> recordById(final Long id) { final ConfigurationAttributeRecord record = this.configurationAttributeRecordMapper
return Result.tryCatch(() -> { .selectByPrimaryKey(id);
final ConfigurationAttributeRecord record = this.configurationAttributeRecordMapper if (record == null) {
.selectByPrimaryKey(id); throw new ResourceNotFoundException(
if (record == null) { EntityType.CONFIGURATION_ATTRIBUTE,
throw new ResourceNotFoundException( String.valueOf(id));
EntityType.CONFIGURATION_ATTRIBUTE, }
String.valueOf(id)); return record;
} });
return record; }
});
} static Result<ConfigurationAttribute> toDomainModel(final ConfigurationAttributeRecord record) {
return Result.tryCatch(() -> new ConfigurationAttribute(
static Result<ConfigurationAttribute> toDomainModel(final ConfigurationAttributeRecord record) { record.getId(),
return Result.tryCatch(() -> new ConfigurationAttribute( record.getParentId(),
record.getId(), record.getName(),
record.getParentId(), AttributeType.valueOf(record.getType()),
record.getName(), record.getResources(),
AttributeType.valueOf(record.getType()), record.getValidator(),
record.getResources(), record.getDependencies(),
record.getValidator(), record.getDefaultValue()));
record.getDependencies(), }
record.getDefaultValue()));
} }
}

View file

@ -1,494 +1,480 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.*; import static org.mybatis.dynamic.sql.SqlBuilder.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.SqlBuilder; import org.mybatis.dynamic.sql.SqlBuilder;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType; import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap; import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationNodeRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationNodeRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationNodeRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationNodeRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ConfigurationRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamConfigurationMapRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordMapper; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ExamRecordMapper;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationNodeRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ConfigurationNodeRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamConfigurationMapRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamConfigurationMapRecord;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ExamRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@Lazy @Lazy
@Component @Component
@WebServiceProfile @WebServiceProfile
public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO { public class ExamConfigurationMapDAOImpl implements ExamConfigurationMapDAO {
private final ExamRecordMapper examRecordMapper; private final ExamRecordMapper examRecordMapper;
private final ExamConfigurationMapRecordMapper examConfigurationMapRecordMapper; private final ExamConfigurationMapRecordMapper examConfigurationMapRecordMapper;
private final ConfigurationNodeRecordMapper configurationNodeRecordMapper; private final ConfigurationNodeRecordMapper configurationNodeRecordMapper;
private final ClientCredentialService clientCredentialService; private final ClientCredentialService clientCredentialService;
private final ExamDAO examDAO; private final ExamDAO examDAO;
protected ExamConfigurationMapDAOImpl( protected ExamConfigurationMapDAOImpl(
final ExamRecordMapper examRecordMapper, final ExamRecordMapper examRecordMapper,
final ExamConfigurationMapRecordMapper examConfigurationMapRecordMapper, final ExamConfigurationMapRecordMapper examConfigurationMapRecordMapper,
final ConfigurationNodeRecordMapper configurationNodeRecordMapper, final ConfigurationNodeRecordMapper configurationNodeRecordMapper,
final ClientCredentialService clientCredentialService, final ClientCredentialService clientCredentialService,
final ExamDAO examDAO) { final ExamDAO examDAO) {
this.examRecordMapper = examRecordMapper; this.examRecordMapper = examRecordMapper;
this.examConfigurationMapRecordMapper = examConfigurationMapRecordMapper; this.examConfigurationMapRecordMapper = examConfigurationMapRecordMapper;
this.configurationNodeRecordMapper = configurationNodeRecordMapper; this.configurationNodeRecordMapper = configurationNodeRecordMapper;
this.clientCredentialService = clientCredentialService; this.clientCredentialService = clientCredentialService;
this.examDAO = examDAO; this.examDAO = examDAO;
} }
@Override @Override
public EntityType entityType() { public EntityType entityType() {
return EntityType.EXAM_CONFIGURATION_MAP; return EntityType.EXAM_CONFIGURATION_MAP;
} }
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Result<ExamConfigurationMap> byPK(final Long id) { public Result<ExamConfigurationMap> byPK(final Long id) {
return recordById(id) return recordById(id)
.flatMap(this::toDomainModel); .flatMap(this::toDomainModel);
} }
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Result<Collection<ExamConfigurationMap>> allOf(final Set<Long> pks) { public Result<Collection<ExamConfigurationMap>> allOf(final Set<Long> pks) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectByExample()
return this.examConfigurationMapRecordMapper.selectByExample() .where(ExamConfigurationMapRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.where(ExamConfigurationMapRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks))) .build()
.build() .execute()
.execute() .stream()
.stream() .map(this::toDomainModel)
.map(this::toDomainModel) .flatMap(DAOLoggingSupport::logAndSkipOnError)
.flatMap(DAOLoggingSupport::logAndSkipOnError) .collect(Collectors.toList()));
.collect(Collectors.toList()); }
});
} @Override
@Transactional(readOnly = true)
@Override public Result<Collection<ExamConfigurationMap>> allMatching(
@Transactional(readOnly = true) final FilterMap filterMap,
public Result<Collection<ExamConfigurationMap>> allMatching( final Predicate<ExamConfigurationMap> predicate) {
final FilterMap filterMap,
final Predicate<ExamConfigurationMap> predicate) { return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
.selectByExample()
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper .where(
.selectByExample() ExamConfigurationMapRecordDynamicSqlSupport.institutionId,
.where( SqlBuilder.isEqualToWhenPresent(filterMap.getInstitutionId()))
ExamConfigurationMapRecordDynamicSqlSupport.institutionId, .and(
SqlBuilder.isEqualToWhenPresent(filterMap.getInstitutionId())) ExamConfigurationMapRecordDynamicSqlSupport.examId,
.and( SqlBuilder.isEqualToWhenPresent(filterMap.getExamConfigExamId()))
ExamConfigurationMapRecordDynamicSqlSupport.examId, .and(
SqlBuilder.isEqualToWhenPresent(filterMap.getExamConfigExamId())) ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
.and( SqlBuilder.isEqualToWhenPresent(filterMap.getExamConfigConfigId()))
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId, .build()
SqlBuilder.isEqualToWhenPresent(filterMap.getExamConfigConfigId())) .execute()
.build() .stream()
.execute() .map(this::toDomainModel)
.stream() .flatMap(DAOLoggingSupport::logAndSkipOnError)
.map(this::toDomainModel) .filter(predicate)
.flatMap(DAOLoggingSupport::logAndSkipOnError) .collect(Collectors.toList()));
.filter(predicate) }
.collect(Collectors.toList()));
} @Override
@Transactional(readOnly = true)
@Override public Result<ExamConfigurationMap> byMapping(final Long examId, final Long configurationNodeId) {
@Transactional(readOnly = true) return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
public Result<ExamConfigurationMap> byMapping(final Long examId, final Long configurationNodeId) { .selectByExample()
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper .where(
.selectByExample() ExamConfigurationMapRecordDynamicSqlSupport.examId,
.where( SqlBuilder.isEqualTo(examId))
ExamConfigurationMapRecordDynamicSqlSupport.examId, .and(
SqlBuilder.isEqualTo(examId)) ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
.and( SqlBuilder.isEqualTo(configurationNodeId))
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId, .build()
SqlBuilder.isEqualTo(configurationNodeId)) .execute()
.build() .stream()
.execute() .map(this::toDomainModel)
.stream() .flatMap(DAOLoggingSupport::logAndSkipOnError)
.map(this::toDomainModel) .collect(Utils.toSingleton()));
.flatMap(DAOLoggingSupport::logAndSkipOnError) }
.collect(Utils.toSingleton()));
} @Override
@Transactional(readOnly = true)
@Override public Result<CharSequence> getConfigPasswordCipher(final Long examId, final Long configurationNodeId) {
@Transactional(readOnly = true) return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
public Result<CharSequence> getConfigPasswortCipher(final Long examId, final Long configurationNodeId) { .selectByExample()
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper .where(
.selectByExample() ExamConfigurationMapRecordDynamicSqlSupport.examId,
.where( SqlBuilder.isEqualTo(examId))
ExamConfigurationMapRecordDynamicSqlSupport.examId, .and(
SqlBuilder.isEqualTo(examId)) ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
.and( SqlBuilder.isEqualTo(configurationNodeId))
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId, .build()
SqlBuilder.isEqualTo(configurationNodeId)) .execute()
.build() .stream()
.execute() .collect(Utils.toSingleton()))
.stream() .map(ExamConfigurationMapRecord::getEncryptSecret);
.collect(Utils.toSingleton())) }
.map(ExamConfigurationMapRecord::getEncryptSecret);
} @Override
@Transactional(readOnly = true)
@Override public Result<Long> getDefaultConfigurationNode(final Long examId) {
@Transactional(readOnly = true) return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
public Result<Long> getDefaultConfigurationNode(final Long examId) { .selectByExample()
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper .where(
.selectByExample() ExamConfigurationMapRecordDynamicSqlSupport.examId,
.where( SqlBuilder.isEqualTo(examId))
ExamConfigurationMapRecordDynamicSqlSupport.examId, .and(
SqlBuilder.isEqualTo(examId)) ExamConfigurationMapRecordDynamicSqlSupport.userNames,
.and( SqlBuilder.isNull())
ExamConfigurationMapRecordDynamicSqlSupport.userNames, .build()
SqlBuilder.isNull()) .execute()
.build() .stream()
.execute() .map(ExamConfigurationMapRecord::getConfigurationNodeId)
.stream() .collect(Utils.toSingleton()));
.map(mapping -> mapping.getConfigurationNodeId()) }
.collect(Utils.toSingleton()));
} @Override
@Transactional(readOnly = true)
@Override public Result<Long> getUserConfigurationNodeId(final Long examId, final String userId) {
@Transactional(readOnly = true) return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
public Result<Long> getUserConfigurationNodeId(final Long examId, final String userId) { .selectByExample()
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper .where(
.selectByExample() ExamConfigurationMapRecordDynamicSqlSupport.examId,
.where( SqlBuilder.isEqualTo(examId))
ExamConfigurationMapRecordDynamicSqlSupport.examId, .and(
SqlBuilder.isEqualTo(examId)) ExamConfigurationMapRecordDynamicSqlSupport.userNames,
.and( SqlBuilder.isLike(Utils.toSQLWildcard(userId)))
ExamConfigurationMapRecordDynamicSqlSupport.userNames, .build()
SqlBuilder.isLike(Utils.toSQLWildcard(userId))) .execute()
.build() .stream()
.execute() .map(ExamConfigurationMapRecord::getConfigurationNodeId)
.stream() .collect(Utils.toSingleton()));
.map(mapping -> mapping.getConfigurationNodeId()) }
.collect(Utils.toSingleton()));
} @Override
@Transactional(readOnly = true)
@Override public Result<Collection<Long>> getConfigurationNodeIds(final Long examId) {
@Transactional(readOnly = true) return Result.tryCatch(() -> this.examConfigurationMapRecordMapper
public Result<Collection<Long>> getConfigurationNodeIds(final Long examId) { .selectByExample()
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper .where(
.selectByExample() ExamConfigurationMapRecordDynamicSqlSupport.examId,
.where( SqlBuilder.isEqualTo(examId))
ExamConfigurationMapRecordDynamicSqlSupport.examId, .build()
SqlBuilder.isEqualTo(examId)) .execute()
.build() .stream()
.execute() .map(ExamConfigurationMapRecord::getConfigurationNodeId)
.stream() .collect(Collectors.toList()));
.map(mapping -> mapping.getConfigurationNodeId()) }
.collect(Collectors.toList()));
} @Override
@Transactional
@Override public Result<ExamConfigurationMap> createNew(final ExamConfigurationMap data) {
@Transactional return checkMappingIntegrity(data)
public Result<ExamConfigurationMap> createNew(final ExamConfigurationMap data) { .map(config -> {
return checkMappingIntegrity(data) final ExamConfigurationMapRecord newRecord = new ExamConfigurationMapRecord(
.map(config -> { null,
final ExamConfigurationMapRecord newRecord = new ExamConfigurationMapRecord( data.institutionId,
null, data.examId,
data.institutionId, data.configurationNodeId,
data.examId, data.userNames,
data.configurationNodeId, getEncryptionPassword(data));
data.userNames,
getEncryptionPassword(data)); this.examConfigurationMapRecordMapper.insert(newRecord);
return newRecord;
this.examConfigurationMapRecordMapper.insert(newRecord); })
return newRecord; .flatMap(this::toDomainModel)
}) .onError(TransactionHandler::rollback);
.flatMap(this::toDomainModel) }
.onError(TransactionHandler::rollback);
} @Override
@Transactional
@Override public Result<ExamConfigurationMap> save(final ExamConfigurationMap data) {
@Transactional return Result.tryCatch(() -> {
public Result<ExamConfigurationMap> save(final ExamConfigurationMap data) {
return Result.tryCatch(() -> { final ExamConfigurationMapRecord newRecord = new ExamConfigurationMapRecord(
data.id,
final ExamConfigurationMapRecord newRecord = new ExamConfigurationMapRecord( null,
data.id, null,
null, null,
null, data.userNames,
null, getEncryptionPassword(data));
data.userNames,
getEncryptionPassword(data)); this.examConfigurationMapRecordMapper.updateByPrimaryKeySelective(newRecord);
return this.examConfigurationMapRecordMapper.selectByPrimaryKey(data.id);
this.examConfigurationMapRecordMapper.updateByPrimaryKeySelective(newRecord); })
return this.examConfigurationMapRecordMapper.selectByPrimaryKey(data.id); .flatMap(this::toDomainModel)
}) .onError(TransactionHandler::rollback);
.flatMap(this::toDomainModel) }
.onError(TransactionHandler::rollback);
} @Override
@Transactional
@Override public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
@Transactional return Result.tryCatch(() -> {
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
return Result.tryCatch(() -> { final List<Long> ids = extractListOfPKs(all);
final List<Long> ids = extractListOfPKs(all); this.examConfigurationMapRecordMapper.deleteByExample()
.where(ExamConfigurationMapRecordDynamicSqlSupport.id, isIn(ids))
this.examConfigurationMapRecordMapper.deleteByExample() .build()
.where(ExamConfigurationMapRecordDynamicSqlSupport.id, isIn(ids)) .execute();
.build()
.execute(); return ids.stream()
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
return ids.stream() .collect(Collectors.toList());
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP)) });
.collect(Collectors.toList()); }
});
} @Override
@Transactional(readOnly = true)
@Override public Set<EntityKey> getDependencies(final BulkAction bulkAction) {
@Transactional(readOnly = true) if (bulkAction.type == BulkActionType.ACTIVATE || bulkAction.type == BulkActionType.DEACTIVATE) {
public Set<EntityKey> getDependencies(final BulkAction bulkAction) { return Collections.emptySet();
if (bulkAction.type == BulkActionType.ACTIVATE || bulkAction.type == BulkActionType.DEACTIVATE) { }
return Collections.emptySet();
} // define the select function in case of source type
Function<EntityKey, Result<Collection<EntityKey>>> selectionFunction;
// define the select function in case of source type switch (bulkAction.sourceType) {
Function<EntityKey, Result<Collection<EntityKey>>> selectionFunction; case INSTITUTION:
switch (bulkAction.sourceType) { selectionFunction = this::allIdsOfInstitution;
case INSTITUTION: break;
selectionFunction = this::allIdsOfInstitution; case LMS_SETUP:
break; selectionFunction = this::allIdsOfLmsSetup;
case LMS_SETUP: break;
selectionFunction = this::allIdsOfLmsSetup; case EXAM:
break; selectionFunction = this::allIdsOfExam;
case EXAM: break;
selectionFunction = this::allIdsOfExam; case CONFIGURATION_NODE:
break; selectionFunction = this::allIdsOfConfig;
case CONFIGURATION_NODE: break;
selectionFunction = this::allIdsOfConfig; default:
break; selectionFunction = key -> Result.of(Collections.emptyList()); //empty select function
default: break;
selectionFunction = key -> Result.of(Collections.emptyList()); //empty select function }
break;
} return getDependencies(bulkAction, selectionFunction);
}
return getDependencies(bulkAction, selectionFunction);
} @Override
@Transactional(readOnly = true)
@Override public Result<Collection<Long>> getExamIdsForConfigNodeId(final Long configurationNodeId) {
@Transactional(readOnly = true) return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectByExample()
public Result<Collection<Long>> getExamIdsForConfigNodeId(final Long configurationNodeId) { .where(
return Result.tryCatch(() -> { ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
return this.examConfigurationMapRecordMapper.selectByExample() isEqualTo(configurationNodeId))
.where( .build()
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId, .execute()
isEqualTo(configurationNodeId)) .stream()
.build() .map(ExamConfigurationMapRecord::getExamId)
.execute() .collect(Collectors.toList()));
.stream() }
.map(record -> record.getExamId())
.collect(Collectors.toList()); @Override
}); @Transactional(readOnly = true)
} public Result<Collection<Long>> getExamIdsForConfigId(final Long configurationId) {
return Result.tryCatch(() -> this.configurationNodeRecordMapper.selectIdsByExample()
@Override .leftJoin(ConfigurationRecordDynamicSqlSupport.configurationRecord)
@Transactional(readOnly = true) .on(
public Result<Collection<Long>> getExamIdsForConfigId(final Long configurationId) { ConfigurationRecordDynamicSqlSupport.configurationNodeId,
return Result.tryCatch(() -> { equalTo(ConfigurationNodeRecordDynamicSqlSupport.id))
return this.configurationNodeRecordMapper.selectIdsByExample() .where(
.leftJoin(ConfigurationRecordDynamicSqlSupport.configurationRecord) ConfigurationRecordDynamicSqlSupport.id,
.on( isEqualTo(configurationId))
ConfigurationRecordDynamicSqlSupport.configurationNodeId, .build()
equalTo(ConfigurationNodeRecordDynamicSqlSupport.id)) .execute()
.where( .stream()
ConfigurationRecordDynamicSqlSupport.id, .collect(Utils.toSingleton()))
isEqualTo(configurationId)) .flatMap(this::getExamIdsForConfigNodeId);
.build() }
.execute()
.stream() private Result<ExamConfigurationMapRecord> recordById(final Long id) {
.collect(Utils.toSingleton()); return Result.tryCatch(() -> {
}) final ExamConfigurationMapRecord record = this.examConfigurationMapRecordMapper
.flatMap(this::getExamIdsForConfigNodeId); .selectByPrimaryKey(id);
} if (record == null) {
throw new ResourceNotFoundException(
private Result<ExamConfigurationMapRecord> recordById(final Long id) { EntityType.EXAM_CONFIGURATION_MAP,
return Result.tryCatch(() -> { String.valueOf(id));
final ExamConfigurationMapRecord record = this.examConfigurationMapRecordMapper }
.selectByPrimaryKey(id); return record;
if (record == null) { });
throw new ResourceNotFoundException( }
EntityType.EXAM_CONFIGURATION_MAP,
String.valueOf(id)); private Result<ExamConfigurationMap> toDomainModel(final ExamConfigurationMapRecord record) {
} return Result.tryCatch(() -> {
return record;
}); final ConfigurationNodeRecord config = this.configurationNodeRecordMapper
} .selectByPrimaryKey(record.getConfigurationNodeId());
final String status = config.getStatus();
private Result<ExamConfigurationMap> toDomainModel(final ExamConfigurationMapRecord record) {
return Result.tryCatch(() -> { final Exam exam = this.examDAO.byPK(record.getExamId())
.getOr(null);
final ConfigurationNodeRecord config = this.configurationNodeRecordMapper
.selectByPrimaryKey(record.getConfigurationNodeId()); return new ExamConfigurationMap(
final String status = config.getStatus(); record.getId(),
record.getInstitutionId(),
final Exam exam = this.examDAO.byPK(record.getExamId()) record.getExamId(),
.getOr(null); (exam != null) ? exam.name : null,
(exam != null) ? exam.description : null,
return new ExamConfigurationMap( (exam != null) ? exam.startTime : null,
record.getId(), (exam != null) ? exam.type : ExamType.UNDEFINED,
record.getInstitutionId(), record.getConfigurationNodeId(),
record.getExamId(), record.getUserNames(),
(exam != null) ? exam.name : null, record.getEncryptSecret(),
(exam != null) ? exam.description : null, null,
(exam != null) ? exam.startTime : null, config.getName(),
(exam != null) ? exam.type : ExamType.UNDEFINED, config.getDescription(),
record.getConfigurationNodeId(), (StringUtils.isNotBlank(status)) ? ConfigurationStatus.valueOf(status) : null);
record.getUserNames(), });
null, }
null,
config.getName(), private Result<ExamConfigurationMap> checkMappingIntegrity(final ExamConfigurationMap data) {
config.getDescription(), return Result.tryCatch(() -> {
(StringUtils.isNotBlank(status)) ? ConfigurationStatus.valueOf(status) : null); final ConfigurationNodeRecord config =
}); this.configurationNodeRecordMapper.selectByPrimaryKey(data.configurationNodeId);
}
if (config == null) {
private Result<ExamConfigurationMap> checkMappingIntegrity(final ExamConfigurationMap data) { throw new ResourceNotFoundException(
return Result.tryCatch(() -> { EntityType.CONFIGURATION_NODE,
final ConfigurationNodeRecord config = String.valueOf(data.configurationNodeId));
this.configurationNodeRecordMapper.selectByPrimaryKey(data.configurationNodeId); }
if (config == null) { if (config.getInstitutionId().longValue() != data.institutionId.longValue()) {
throw new ResourceNotFoundException( throw new IllegalArgumentException("Institutional integrity constraint violation");
EntityType.CONFIGURATION_NODE, }
String.valueOf(data.configurationNodeId));
} final ExamRecord exam = this.examRecordMapper.selectByPrimaryKey(data.examId);
if (config.getInstitutionId().longValue() != data.institutionId.longValue()) { if (exam == null) {
throw new IllegalArgumentException("Institutional integrity constraint violation"); throw new ResourceNotFoundException(
} EntityType.EXAM,
String.valueOf(data.configurationNodeId));
final ExamRecord exam = this.examRecordMapper.selectByPrimaryKey(data.examId); }
if (exam == null) { if (exam.getInstitutionId().longValue() != data.institutionId.longValue()) {
throw new ResourceNotFoundException( throw new IllegalArgumentException("Institutional integrity constraint violation");
EntityType.EXAM, }
String.valueOf(data.configurationNodeId));
} return data;
});
if (exam.getInstitutionId().longValue() != data.institutionId.longValue()) { }
throw new IllegalArgumentException("Institutional integrity constraint violation");
} private Result<Collection<EntityKey>> allIdsOfInstitution(final EntityKey institutionKey) {
return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample()
return data; .where(
}); ExamConfigurationMapRecordDynamicSqlSupport.institutionId,
} isEqualTo(Long.valueOf(institutionKey.modelId)))
.build()
private Result<Collection<EntityKey>> allIdsOfInstitution(final EntityKey institutionKey) { .execute()
return Result.tryCatch(() -> { .stream()
return this.examConfigurationMapRecordMapper.selectIdsByExample() .map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
.where( .collect(Collectors.toList()));
ExamConfigurationMapRecordDynamicSqlSupport.institutionId, }
isEqualTo(Long.valueOf(institutionKey.modelId)))
.build() private Result<Collection<EntityKey>> allIdsOfLmsSetup(final EntityKey lmsSetupKey) {
.execute() return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample()
.stream() .leftJoin(ExamRecordDynamicSqlSupport.examRecord)
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP)) .on(
.collect(Collectors.toList()); ExamRecordDynamicSqlSupport.id,
}); equalTo(ExamConfigurationMapRecordDynamicSqlSupport.examId))
}
.where(
private Result<Collection<EntityKey>> allIdsOfLmsSetup(final EntityKey lmsSetupKey) { ExamRecordDynamicSqlSupport.lmsSetupId,
return Result.tryCatch(() -> { isEqualTo(Long.valueOf(lmsSetupKey.modelId)))
return this.examConfigurationMapRecordMapper.selectIdsByExample() .build()
.leftJoin(ExamRecordDynamicSqlSupport.examRecord) .execute()
.on( .stream()
ExamRecordDynamicSqlSupport.id, .map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
equalTo(ExamConfigurationMapRecordDynamicSqlSupport.examId)) .collect(Collectors.toList()));
}
.where(
ExamRecordDynamicSqlSupport.lmsSetupId, private Result<Collection<EntityKey>> allIdsOfExam(final EntityKey examKey) {
isEqualTo(Long.valueOf(lmsSetupKey.modelId))) return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample()
.build() .where(
.execute() ExamConfigurationMapRecordDynamicSqlSupport.examId,
.stream() isEqualTo(Long.valueOf(examKey.modelId)))
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP)) .build()
.collect(Collectors.toList()); .execute()
}); .stream()
} .map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
.collect(Collectors.toList()));
private Result<Collection<EntityKey>> allIdsOfExam(final EntityKey examKey) { }
return Result.tryCatch(() -> {
return this.examConfigurationMapRecordMapper.selectIdsByExample() private Result<Collection<EntityKey>> allIdsOfConfig(final EntityKey configKey) {
.where( return Result.tryCatch(() -> this.examConfigurationMapRecordMapper.selectIdsByExample()
ExamConfigurationMapRecordDynamicSqlSupport.examId, .where(
isEqualTo(Long.valueOf(examKey.modelId))) ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId,
.build() isEqualTo(Long.valueOf(configKey.modelId)))
.execute() .build()
.stream() .execute()
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP)) .stream()
.collect(Collectors.toList()); .map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP))
}); .collect(Collectors.toList()));
} }
private Result<Collection<EntityKey>> allIdsOfConfig(final EntityKey configKey) { private String getEncryptionPassword(final ExamConfigurationMap examConfigurationMap) {
return Result.tryCatch(() -> { if (examConfigurationMap.hasEncryptionSecret() &&
return this.examConfigurationMapRecordMapper.selectIdsByExample() !examConfigurationMap.encryptSecret.equals(examConfigurationMap.confirmEncryptSecret)) {
.where( throw new APIMessageException(ErrorMessage.PASSWORD_MISMATCH);
ExamConfigurationMapRecordDynamicSqlSupport.configurationNodeId, }
isEqualTo(Long.valueOf(configKey.modelId)))
.build() final CharSequence encrypted_encrypt_secret = examConfigurationMap.hasEncryptionSecret()
.execute() ? this.clientCredentialService.encrypt(examConfigurationMap.encryptSecret)
.stream() : null;
.map(id -> new EntityKey(id, EntityType.EXAM_CONFIGURATION_MAP)) return (encrypted_encrypt_secret != null) ? encrypted_encrypt_secret.toString() : null;
.collect(Collectors.toList()); }
});
} }
private String getEncryptionPassword(final ExamConfigurationMap examConfigurationMap) {
if (examConfigurationMap.hasEncryptionSecret() &&
!examConfigurationMap.encryptSecret.equals(examConfigurationMap.confirmEncryptSecret)) {
throw new APIMessageException(ErrorMessage.PASSWORD_MISMATCH);
}
final CharSequence encrypted_encrypt_secret = examConfigurationMap.hasEncryptionSecret()
? this.clientCredentialService.encrypt(examConfigurationMap.encryptSecret)
: null;
return (encrypted_encrypt_secret != null) ? encrypted_encrypt_secret.toString() : null;
}
}

View file

@ -1,430 +1,526 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl;
import static org.mybatis.dynamic.sql.SqlBuilder.*; import static org.mybatis.dynamic.sql.SqlBuilder.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Map;
import java.util.function.Predicate; import java.util.Set;
import java.util.stream.Collectors; import java.util.function.Function;
import java.util.function.Predicate;
import org.apache.commons.lang3.BooleanUtils; import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime; import org.apache.commons.lang3.BooleanUtils;
import org.joda.time.DateTimeZone; import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy; import org.joda.time.DateTime;
import org.springframework.stereotype.Component; import org.joda.time.DateTimeZone;
import org.springframework.transaction.annotation.Transactional; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import org.springframework.transaction.annotation.Transactional;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig.ConfigPurpose;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordMapper; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.SebClientConfigRecord; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordMapper;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.AdditionalAttributeRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials; import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.SebClientConfigRecord;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SebClientConfigDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ResourceNotFoundException;
@Lazy import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SebClientConfigDAO;
@Component import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
@WebServiceProfile
public class SebClientConfigDAOImpl implements SebClientConfigDAO { @Lazy
@Component
private final SebClientConfigRecordMapper sebClientConfigRecordMapper; @WebServiceProfile
private final ClientCredentialService clientCredentialService; public class SebClientConfigDAOImpl implements SebClientConfigDAO {
private final AdditionalAttributesDAOImpl additionalAttributesDAO;
private final SebClientConfigRecordMapper sebClientConfigRecordMapper;
protected SebClientConfigDAOImpl( private final ClientCredentialService clientCredentialService;
final SebClientConfigRecordMapper sebClientConfigRecordMapper, private final AdditionalAttributesDAOImpl additionalAttributesDAO;
final ClientCredentialService clientCredentialService,
final AdditionalAttributesDAOImpl additionalAttributesDAO) { protected SebClientConfigDAOImpl(
final SebClientConfigRecordMapper sebClientConfigRecordMapper,
this.sebClientConfigRecordMapper = sebClientConfigRecordMapper; final ClientCredentialService clientCredentialService,
this.clientCredentialService = clientCredentialService; final AdditionalAttributesDAOImpl additionalAttributesDAO) {
this.additionalAttributesDAO = additionalAttributesDAO;
} this.sebClientConfigRecordMapper = sebClientConfigRecordMapper;
this.clientCredentialService = clientCredentialService;
@Override this.additionalAttributesDAO = additionalAttributesDAO;
public EntityType entityType() { }
return EntityType.SEB_CLIENT_CONFIGURATION;
} @Override
public EntityType entityType() {
@Override return EntityType.SEB_CLIENT_CONFIGURATION;
@Transactional(readOnly = true) }
public Result<SebClientConfig> byPK(final Long id) {
return recordById(id) @Override
.flatMap(this::toDomainModel); @Transactional(readOnly = true)
} public Result<SebClientConfig> byPK(final Long id) {
return recordById(id)
@Override .flatMap(this::toDomainModel);
@Transactional(readOnly = true) }
public Result<Collection<SebClientConfig>> all(final Long institutionId, final Boolean active) {
return Result.tryCatch(() -> { @Override
@Transactional(readOnly = true)
final List<SebClientConfigRecord> records = (active != null) public Result<Collection<SebClientConfig>> all(final Long institutionId, final Boolean active) {
? this.sebClientConfigRecordMapper.selectByExample() return Result.tryCatch(() -> {
.where(
SebClientConfigRecordDynamicSqlSupport.institutionId, final List<SebClientConfigRecord> records = (active != null)
isEqualToWhenPresent(institutionId)) ? this.sebClientConfigRecordMapper.selectByExample()
.and( .where(
SebClientConfigRecordDynamicSqlSupport.active, SebClientConfigRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(BooleanUtils.toIntegerObject(active))) isEqualToWhenPresent(institutionId))
.build() .and(
.execute() SebClientConfigRecordDynamicSqlSupport.active,
: this.sebClientConfigRecordMapper.selectByExample().build().execute(); isEqualToWhenPresent(BooleanUtils.toIntegerObject(active)))
.build()
return records.stream() .execute()
.map(this::toDomainModel) : this.sebClientConfigRecordMapper.selectByExample().build().execute();
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList()); return records.stream()
}); .map(this::toDomainModel)
} .flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList());
@Override });
@Transactional(readOnly = true) }
public Result<Collection<SebClientConfig>> allMatching(
final FilterMap filterMap, @Override
final Predicate<SebClientConfig> predicate) { @Transactional(readOnly = true)
public Result<Collection<SebClientConfig>> allMatching(
return Result.tryCatch(() -> { final FilterMap filterMap,
final Predicate<SebClientConfig> predicate) {
return this.sebClientConfigRecordMapper
.selectByExample() return Result.tryCatch(() -> this.sebClientConfigRecordMapper
.where( .selectByExample()
SebClientConfigRecordDynamicSqlSupport.institutionId, .where(
isEqualToWhenPresent(filterMap.getInstitutionId())) SebClientConfigRecordDynamicSqlSupport.institutionId,
.and( isEqualToWhenPresent(filterMap.getInstitutionId()))
SebClientConfigRecordDynamicSqlSupport.name, .and(
isLikeWhenPresent(filterMap.getName())) SebClientConfigRecordDynamicSqlSupport.name,
.and( isLikeWhenPresent(filterMap.getName()))
SebClientConfigRecordDynamicSqlSupport.date, .and(
isGreaterThanOrEqualToWhenPresent(filterMap.getSebClientConfigFromTime())) SebClientConfigRecordDynamicSqlSupport.date,
.and( isGreaterThanOrEqualToWhenPresent(filterMap.getSebClientConfigFromTime()))
SebClientConfigRecordDynamicSqlSupport.active, .and(
isEqualToWhenPresent(filterMap.getActiveAsInt())) SebClientConfigRecordDynamicSqlSupport.active,
.build() isEqualToWhenPresent(filterMap.getActiveAsInt()))
.execute() .build()
.stream() .execute()
.map(this::toDomainModel) .stream()
.flatMap(DAOLoggingSupport::logAndSkipOnError) .map(this::toDomainModel)
.filter(predicate) .flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList()); .filter(predicate)
}); .collect(Collectors.toList()));
} }
@Override @Override
public Result<SebClientConfig> byClientName(final String clientName) { public Result<SebClientConfig> byClientName(final String clientName) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> this.sebClientConfigRecordMapper
.selectByExample()
return this.sebClientConfigRecordMapper .where(
.selectByExample() SebClientConfigRecordDynamicSqlSupport.clientName,
.where( isEqualTo(clientName))
SebClientConfigRecordDynamicSqlSupport.clientName, .build()
isEqualTo(clientName)) .execute()
.build() .stream()
.execute() .map(this::toDomainModel)
.stream() .flatMap(DAOLoggingSupport::logAndSkipOnError)
.map(this::toDomainModel) .collect(Utils.toSingleton()));
.flatMap(DAOLoggingSupport::logAndSkipOnError) }
.collect(Utils.toSingleton());
}); @Override
} @Transactional(readOnly = true)
public Result<CharSequence> getConfigPasswordCipherByClientName(final String clientName) {
@Override return Result.tryCatch(() -> {
@Transactional(readOnly = true)
public Result<CharSequence> getConfigPasswortCipherByClientName(final String clientName) { final SebClientConfigRecord record = this.sebClientConfigRecordMapper
return Result.tryCatch(() -> { .selectByExample()
.where(
final SebClientConfigRecord record = this.sebClientConfigRecordMapper SebClientConfigRecordDynamicSqlSupport.clientName,
.selectByExample() isEqualTo(clientName))
.where( .and(
SebClientConfigRecordDynamicSqlSupport.clientName, SebClientConfigRecordDynamicSqlSupport.active,
isEqualTo(clientName)) isNotEqualTo(0))
.and( .build()
SebClientConfigRecordDynamicSqlSupport.active, .execute()
isNotEqualTo(0)) .stream()
.build() .collect(Utils.toSingleton());
.execute()
.stream() return record.getClientSecret();
.collect(Utils.toSingleton()); });
}
return record.getClientSecret();
}); @Override
} @Transactional(readOnly = true)
public boolean isActive(final String modelId) {
@Override if (StringUtils.isBlank(modelId)) {
@Transactional(readOnly = true) return false;
public boolean isActive(final String modelId) { }
if (StringUtils.isBlank(modelId)) {
return false; return this.sebClientConfigRecordMapper.countByExample()
} .where(SebClientConfigRecordDynamicSqlSupport.id, isEqualTo(Long.valueOf(modelId)))
.and(SebClientConfigRecordDynamicSqlSupport.active, isEqualTo(BooleanUtils.toInteger(true)))
return this.sebClientConfigRecordMapper.countByExample() .build()
.where(SebClientConfigRecordDynamicSqlSupport.id, isEqualTo(Long.valueOf(modelId))) .execute() > 0;
.and(SebClientConfigRecordDynamicSqlSupport.active, isEqualTo(BooleanUtils.toInteger(true))) }
.build()
.execute() @Override
.longValue() > 0; @Transactional
} public Result<Collection<EntityKey>> setActive(final Set<EntityKey> all, final boolean active) {
return Result.tryCatch(() -> {
@Override
@Transactional final List<Long> ids = extractListOfPKs(all);
public Result<Collection<EntityKey>> setActive(final Set<EntityKey> all, final boolean active) { final SebClientConfigRecord record = new SebClientConfigRecord(
return Result.tryCatch(() -> { null, null, null, null, null, null, null,
BooleanUtils.toIntegerObject(active));
final List<Long> ids = extractListOfPKs(all);
final SebClientConfigRecord record = new SebClientConfigRecord( this.sebClientConfigRecordMapper.updateByExampleSelective(record)
null, null, null, null, null, null, null, .where(SebClientConfigRecordDynamicSqlSupport.id, isIn(ids))
BooleanUtils.toIntegerObject(active)); .build()
.execute();
this.sebClientConfigRecordMapper.updateByExampleSelective(record)
.where(SebClientConfigRecordDynamicSqlSupport.id, isIn(ids)) return ids.stream()
.build() .map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION))
.execute(); .collect(Collectors.toList());
});
return ids.stream() }
.map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION))
.collect(Collectors.toList()); @Override
}); @Transactional
} public Result<SebClientConfig> createNew(final SebClientConfig sebClientConfig) {
return this.clientCredentialService
@Override .generatedClientCredentials()
@Transactional .map(cc -> {
public Result<SebClientConfig> createNew(final SebClientConfig sebClientConfig) {
return this.clientCredentialService checkUniqueName(sebClientConfig);
.generatedClientCredentials()
.map(cc -> { final SebClientConfigRecord newRecord = new SebClientConfigRecord(
null,
checkUniqueName(sebClientConfig); sebClientConfig.institutionId,
sebClientConfig.name,
final SebClientConfigRecord newRecord = new SebClientConfigRecord( DateTime.now(DateTimeZone.UTC),
null, cc.clientIdAsString(),
sebClientConfig.institutionId, cc.secretAsString(),
sebClientConfig.name, getEncryptionPassword(sebClientConfig),
DateTime.now(DateTimeZone.UTC), BooleanUtils.toInteger(BooleanUtils.isTrue(sebClientConfig.active)));
cc.clientIdAsString(),
cc.secretAsString(), this.sebClientConfigRecordMapper
getEncryptionPassword(sebClientConfig), .insert(newRecord);
BooleanUtils.toInteger(BooleanUtils.isTrue(sebClientConfig.active)));
saveAdditionalAttributes(sebClientConfig, newRecord.getId());
this.sebClientConfigRecordMapper
.insert(newRecord); return newRecord;
})
this.additionalAttributesDAO.saveAdditionalAttribute( .flatMap(this::toDomainModel)
EntityType.SEB_CLIENT_CONFIGURATION, .onError(TransactionHandler::rollback);
newRecord.getId(), }
SebClientConfig.ATTR_FALLBACK_START_URL,
sebClientConfig.fallbackStartURL); @Override
@Transactional
return newRecord; public Result<SebClientConfig> save(final SebClientConfig sebClientConfig) {
}) return Result.tryCatch(() -> {
.flatMap(this::toDomainModel)
.onError(TransactionHandler::rollback); checkUniqueName(sebClientConfig);
}
final SebClientConfigRecord newRecord = new SebClientConfigRecord(
@Override sebClientConfig.id,
@Transactional null,
public Result<SebClientConfig> save(final SebClientConfig sebClientConfig) { sebClientConfig.name,
return Result.tryCatch(() -> { null,
null,
checkUniqueName(sebClientConfig); null,
getEncryptionPassword(sebClientConfig),
final SebClientConfigRecord newRecord = new SebClientConfigRecord( null);
sebClientConfig.id,
null, this.sebClientConfigRecordMapper
sebClientConfig.name, .updateByPrimaryKeySelective(newRecord);
null,
null, saveAdditionalAttributes(sebClientConfig, newRecord.getId());
null,
getEncryptionPassword(sebClientConfig), return this.sebClientConfigRecordMapper
null); .selectByPrimaryKey(sebClientConfig.id);
})
this.sebClientConfigRecordMapper .flatMap(this::toDomainModel)
.updateByPrimaryKeySelective(newRecord); .onError(TransactionHandler::rollback);
}
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.SEB_CLIENT_CONFIGURATION, @Override
newRecord.getId(), @Transactional
SebClientConfig.ATTR_FALLBACK_START_URL, public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) {
sebClientConfig.fallbackStartURL); return Result.tryCatch(() -> {
return this.sebClientConfigRecordMapper final List<Long> ids = extractListOfPKs(all);
.selectByPrimaryKey(sebClientConfig.id);
}) this.sebClientConfigRecordMapper.deleteByExample()
.flatMap(this::toDomainModel) .where(SebClientConfigRecordDynamicSqlSupport.id, isIn(ids))
.onError(TransactionHandler::rollback); .build()
} .execute();
@Override return ids.stream()
@Transactional .map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION))
public Result<Collection<EntityKey>> delete(final Set<EntityKey> all) { .collect(Collectors.toList());
return Result.tryCatch(() -> { });
}
final List<Long> ids = extractListOfPKs(all);
@Override
this.sebClientConfigRecordMapper.deleteByExample() @Transactional(readOnly = true)
.where(SebClientConfigRecordDynamicSqlSupport.id, isIn(ids)) public Result<Collection<SebClientConfig>> allOf(final Set<Long> pks) {
.build() return Result.tryCatch(() -> this.sebClientConfigRecordMapper.selectByExample()
.execute(); .where(SebClientConfigRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
.build()
return ids.stream() .execute()
.map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION)) .stream()
.collect(Collectors.toList()); .map(this::toDomainModel)
}); .flatMap(DAOLoggingSupport::logAndSkipOnError)
} .collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true) @Override
public Result<Collection<SebClientConfig>> allOf(final Set<Long> pks) { @Transactional(readOnly = true)
return Result.tryCatch(() -> { public Set<EntityKey> getDependencies(final BulkAction bulkAction) {
// all of institution
return this.sebClientConfigRecordMapper.selectByExample() if (bulkAction.sourceType == EntityType.INSTITUTION) {
.where(SebClientConfigRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks))) return getDependencies(bulkAction, this::allIdsOfInstitution);
.build() }
.execute()
.stream() return Collections.emptySet();
.map(this::toDomainModel) }
.flatMap(DAOLoggingSupport::logAndSkipOnError)
.collect(Collectors.toList()); @Override
}); @Transactional(readOnly = true)
} public Result<ClientCredentials> getSebClientCredentials(final String modelId) {
return recordByModelId(modelId)
@Override .map(rec -> new ClientCredentials(
@Transactional(readOnly = true) rec.getClientName(),
public Set<EntityKey> getDependencies(final BulkAction bulkAction) { rec.getClientSecret(),
// all of institution null));
if (bulkAction.sourceType == EntityType.INSTITUTION) { }
return getDependencies(bulkAction, this::allIdsOfInstitution);
} @Override
@Transactional(readOnly = true)
return Collections.emptySet(); public Result<CharSequence> getConfigPasswordCipher(final String modelId) {
} return recordByModelId(modelId)
.map(SebClientConfigRecord::getEncryptSecret);
@Override }
@Transactional(readOnly = true)
public Result<ClientCredentials> getSebClientCredentials(final String modelId) { private Result<Collection<EntityKey>> allIdsOfInstitution(final EntityKey institutionKey) {
return recordByModelId(modelId) return Result.tryCatch(() -> this.sebClientConfigRecordMapper.selectIdsByExample()
.map(rec -> new ClientCredentials( .where(SebClientConfigRecordDynamicSqlSupport.institutionId,
rec.getClientName(), isEqualTo(Long.valueOf(institutionKey.modelId)))
rec.getClientSecret(), .build()
null)); .execute()
} .stream()
.map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION))
@Override .collect(Collectors.toList()));
@Transactional(readOnly = true) }
public Result<CharSequence> getConfigPasswortCipher(final String modelId) {
return recordByModelId(modelId) private Result<SebClientConfigRecord> recordByModelId(final String modelId) {
.map(rec -> rec.getEncryptSecret()); return Result.tryCatch(() -> recordById(Long.parseLong(modelId)).getOrThrow());
} }
private Result<Collection<EntityKey>> allIdsOfInstitution(final EntityKey institutionKey) { private Result<SebClientConfigRecord> recordById(final Long id) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
return this.sebClientConfigRecordMapper.selectIdsByExample() final SebClientConfigRecord record = this.sebClientConfigRecordMapper
.where(SebClientConfigRecordDynamicSqlSupport.institutionId, .selectByPrimaryKey(id);
isEqualTo(Long.valueOf(institutionKey.modelId)))
.build() if (record == null) {
.execute() throw new ResourceNotFoundException(
.stream() EntityType.SEB_CLIENT_CONFIGURATION,
.map(id -> new EntityKey(id, EntityType.SEB_CLIENT_CONFIGURATION)) String.valueOf(id));
.collect(Collectors.toList()); }
}); return record;
} });
}
private Result<SebClientConfigRecord> recordByModelId(final String modelId) {
return Result.tryCatch(() -> { private Result<SebClientConfig> toDomainModel(final SebClientConfigRecord record) {
return recordById(Long.parseLong(modelId)).getOrThrow();
}); Map<String, AdditionalAttributeRecord> additionalAttributes = this.additionalAttributesDAO
} .getAdditionalAttributes(
EntityType.SEB_CLIENT_CONFIGURATION,
private Result<SebClientConfigRecord> recordById(final Long id) { record.getId())
return Result.tryCatch(() -> { .getOrThrow()
final SebClientConfigRecord record = this.sebClientConfigRecordMapper .stream()
.selectByPrimaryKey(id); .collect(Collectors.toMap(
AdditionalAttributeRecord::getName,
if (record == null) { Function.identity()));
throw new ResourceNotFoundException(
EntityType.SEB_CLIENT_CONFIGURATION, additionalAttributes.get(SebClientConfig.ATTR_CONFIG_PURPOSE);
String.valueOf(id));
} return Result.tryCatch(() -> new SebClientConfig(
return record; record.getId(),
}); record.getInstitutionId(),
} record.getName(),
additionalAttributes.containsKey(SebClientConfig.ATTR_CONFIG_PURPOSE)
private Result<SebClientConfig> toDomainModel(final SebClientConfigRecord record) { ? ConfigPurpose.valueOf(additionalAttributes.get(SebClientConfig.ATTR_CONFIG_PURPOSE).getValue())
final String fallbackURL = this.additionalAttributesDAO.getAdditionalAttributes( : ConfigPurpose.START_EXAM,
EntityType.SEB_CLIENT_CONFIGURATION, additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK) &&
record.getId()) BooleanUtils.toBoolean(additionalAttributes.get(SebClientConfig.ATTR_FALLBACK).getValue()),
.getOrThrow() additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_START_URL)
.stream() ? additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_START_URL).getValue()
.filter(rec -> SebClientConfig.ATTR_FALLBACK_START_URL.equals(rec.getName())) : null,
.findFirst() additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_TIMEOUT)
.map(AdditionalAttributeRecord::getValue) ? Long.parseLong(additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_TIMEOUT).getValue())
.orElse(null); : null,
additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_ATTEMPTS)
return Result.tryCatch(() -> new SebClientConfig( ? Short.parseShort(additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_ATTEMPTS).getValue())
record.getId(), : null,
record.getInstitutionId(), additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL)
record.getName(), ? Short.parseShort(additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL).getValue())
fallbackURL, : null,
record.getDate(), additionalAttributes.containsKey(SebClientConfig.ATTR_FALLBACK_PASSWORD)
null, ? additionalAttributes.get(SebClientConfig.ATTR_FALLBACK_PASSWORD).getValue()
null, : null,
BooleanUtils.toBooleanObject(record.getActive()))); null,
} additionalAttributes.containsKey(SebClientConfig.ATTR_QUIT_PASSWORD)
? additionalAttributes.get(SebClientConfig.ATTR_QUIT_PASSWORD).getValue()
private String getEncryptionPassword(final SebClientConfig sebClientConfig) { : null,
if (sebClientConfig.hasEncryptionSecret() && null,
!sebClientConfig.encryptSecret.equals(sebClientConfig.confirmEncryptSecret)) { record.getDate(),
throw new APIMessageException(ErrorMessage.PASSWORD_MISMATCH); record.getEncryptSecret(),
} null,
BooleanUtils.toBooleanObject(record.getActive())));
final CharSequence encrypted_encrypt_secret = sebClientConfig.hasEncryptionSecret() }
? this.clientCredentialService.encrypt(sebClientConfig.encryptSecret)
: null; private String getEncryptionPassword(final SebClientConfig sebClientConfig) {
return (encrypted_encrypt_secret != null) ? encrypted_encrypt_secret.toString() : null; if (sebClientConfig.hasEncryptionSecret() &&
} !sebClientConfig.encryptSecret.equals(sebClientConfig.encryptSecretConfirm)) {
throw new APIMessageException(ErrorMessage.PASSWORD_MISMATCH);
// check if same name already exists for the same institution }
// if true an APIMessageException with a field validation error is thrown
private void checkUniqueName(final SebClientConfig sebClientConfig) { final CharSequence encrypted_encrypt_secret = sebClientConfig.hasEncryptionSecret()
? this.clientCredentialService.encrypt(sebClientConfig.encryptSecret)
final Long otherWithSameName = this.sebClientConfigRecordMapper : null;
.countByExample() return (encrypted_encrypt_secret != null) ? encrypted_encrypt_secret.toString() : null;
.where(SebClientConfigRecordDynamicSqlSupport.name, isEqualTo(sebClientConfig.name)) }
.and(SebClientConfigRecordDynamicSqlSupport.institutionId, isEqualTo(sebClientConfig.institutionId))
.and(SebClientConfigRecordDynamicSqlSupport.id, isNotEqualToWhenPresent(sebClientConfig.id)) // check if same name already exists for the same institution
.build() // if true an APIMessageException with a field validation error is thrown
.execute(); private void checkUniqueName(final SebClientConfig sebClientConfig) {
if (otherWithSameName != null && otherWithSameName.longValue() > 0) { final Long otherWithSameName = this.sebClientConfigRecordMapper
throw new APIMessageException(APIMessage.fieldValidationError( .countByExample()
Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, .where(SebClientConfigRecordDynamicSqlSupport.name, isEqualTo(sebClientConfig.name))
"clientconfig:name:name.notunique")); .and(SebClientConfigRecordDynamicSqlSupport.institutionId, isEqualTo(sebClientConfig.institutionId))
} .and(SebClientConfigRecordDynamicSqlSupport.id, isNotEqualToWhenPresent(sebClientConfig.id))
} .build()
.execute();
}
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);
}
}
}

View file

@ -1,93 +1,69 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig; package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig;
import java.io.OutputStream; import java.io.OutputStream;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientDetails;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkActionEvent; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkActionEvent;
public interface ClientConfigService { public interface ClientConfigService {
Logger log = LoggerFactory.getLogger(ClientConfigService.class); Logger log = LoggerFactory.getLogger(ClientConfigService.class);
public static final String EXAM_CLIENT_DETAILS_CACHE = "EXAM_CLIENT_DETAILS_CACHE"; String EXAM_CLIENT_DETAILS_CACHE = "EXAM_CLIENT_DETAILS_CACHE";
static String SEB_CLIENT_CONFIG_EXAMPLE_XML = /** Indicates if there is any SebClientConfiguration for a specified institution.
" <dict>\r\n" + *
" <key>sebMode</key>\r\n" + * @param institutionId the institution identifier
" <integer>1</integer>\r\n" + * @return true if there is any SebClientConfiguration for a specified institution. False otherwise */
" <key>sebConfigPurpose</key>\r\n" + boolean hasSebClientConfigurationForInstitution(Long institutionId);
" <integer>1</integer>\r\n" +
" <key>sebServerFallback</key>\r\n" + /** Use this to auto-generate a SebClientConfiguration for a specified institution.
" <%s />\r\n" + * clientName and clientSecret are randomly generated.
" %s" + *
" <key>sebServerURL</key>\r\n" + * @param institutionId the institution identifier
" <string>%s</string>\r\n" + * @return the created SebClientConfig */
" <key>sebServerConfiguration</key>\r\n" + Result<SebClientConfig> autoCreateSebClientConfigurationForInstitution(Long institutionId);
" <dict>\r\n" +
" <key>institution</key>\r\n" + /** Use this to export a specified SebClientConfiguration within a given OutputStream.
" <string>%s</string>\r\n" + * The SEB Client Configuration is exported in the defined SEB Configuration format
" <key>clientName</key>\r\n" + * as described here: https://www.safeexambrowser.org/developer/seb-file-format.html
" <string>%s</string>\r\n" + *
" <key>clientSecret</key>\r\n" + * @param out OutputStream to write the export to
" <string>%s</string>\r\n" + * @param modelId the model identifier of the SebClientConfiguration to export */
" <key>apiDiscovery</key>\r\n" + void exportSebClientConfiguration(
" <string>%s</string>\r\n" + OutputStream out,
" </dict>\r\n" + final String modelId);
" </dict>\r\n";
/** Get the ClientDetails for given client name that identifies a SebClientConfig entry.
/** Indicates if there is any SebClientConfiguration for a specified institution. *
* * @param clientName the client name of a SebClientConfig entry
* @param institutionId the institution identifier * @return Result refer to the ClientDetails for the specified clientName or to an error if happened */
* @return true if there is any SebClientConfiguration for a specified institution. False otherwise */ @Cacheable(
boolean hasSebClientConfigurationForInstitution(Long institutionId); cacheNames = EXAM_CLIENT_DETAILS_CACHE,
key = "#clientName",
/** Use this to auto-generate a SebClientConfiguration for a specified institution. unless = "#result.hasError()")
* clientName and clientSecret are randomly generated. Result<ClientDetails> getClientConfigDetails(String clientName);
*
* @param institutionId the institution identifier @CacheEvict(
* @return the created SebClientConfig */ cacheNames = EXAM_CLIENT_DETAILS_CACHE,
Result<SebClientConfig> autoCreateSebClientConfigurationForInstitution(Long institutionId); allEntries = true)
@EventListener(BulkActionEvent.class)
/** Use this to export a specified SebClientConfiguration within a given OutputStream. void flushClientConfigData(BulkActionEvent event);
* The SEB Client Configuration is exported in the defined SEB Configuration format
* as described here: https://www.safeexambrowser.org/developer/seb-file-format.html }
*
* @param out OutputStream to write the export to
* @param modelId the model identifier of the SebClientConfiguration to export */
void exportSebClientConfiguration(
OutputStream out,
final String modelId);
/** Get the ClientDetails for given client name that identifies a SebClientConfig entry.
*
* @param clientName the client name of a SebClientConfig entry
* @return Result refer to the ClientDetails for the specified clientName or to an error if happened */
@Cacheable(
cacheNames = EXAM_CLIENT_DETAILS_CACHE,
key = "#clientName",
unless = "#result.hasError()")
Result<ClientDetails> getClientConfigDetails(String clientName);
@CacheEvict(
cacheNames = EXAM_CLIENT_DETAILS_CACHE,
allEntries = true)
@EventListener(BulkActionEvent.class)
void flushClientConfigData(BulkActionEvent event);
}

View file

@ -1,314 +1,377 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl;
import java.io.IOException; import ch.ethz.seb.sebserver.WebSecurityConfig;
import java.io.InputStream; import ch.ethz.seb.sebserver.gbl.Constants;
import java.io.OutputStream; import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
import java.io.PipedInputStream; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import java.io.PipedOutputStream; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import java.nio.charset.StandardCharsets; import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
import java.util.Collection; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import java.util.Collections; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import java.util.UUID; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import org.apache.commons.io.IOUtils; import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
import org.apache.commons.lang3.StringUtils; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction;
import org.slf4j.Logger; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkActionEvent;
import org.slf4j.LoggerFactory; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import org.springframework.beans.factory.annotation.Qualifier; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials;
import org.springframework.context.annotation.Lazy; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO;
import org.springframework.security.crypto.password.PasswordEncoder; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SebClientConfigDAO;
import org.springframework.security.oauth2.common.OAuth2AccessToken; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
import org.springframework.security.oauth2.provider.ClientDetails; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService;
import org.springframework.security.oauth2.provider.client.BaseClientDetails; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService.Strategy;
import org.springframework.security.oauth2.provider.token.TokenStore; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService;
import org.springframework.stereotype.Service; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebConfigEncryptionServiceImpl.EncryptionContext;
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebserviceResourceConfiguration;
import ch.ethz.seb.sebserver.WebSecurityConfig; import org.apache.commons.io.IOUtils;
import ch.ethz.seb.sebserver.gbl.Constants; import org.apache.commons.lang3.BooleanUtils;
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType; import org.apache.commons.lang3.StringUtils;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import org.slf4j.Logger;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.model.institution.Institution; import org.springframework.beans.factory.annotation.Qualifier;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; import org.springframework.context.annotation.Lazy;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import org.springframework.security.crypto.password.PasswordEncoder;
import ch.ethz.seb.sebserver.gbl.util.Result; import org.springframework.security.oauth2.common.OAuth2AccessToken;
import ch.ethz.seb.sebserver.gbl.util.Utils; import org.springframework.security.oauth2.provider.ClientDetails;
import ch.ethz.seb.sebserver.webservice.WebserviceInfo; import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkAction; import org.springframework.security.oauth2.provider.token.TokenStore;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.impl.BulkActionEvent; import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentials; import java.io.IOException;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.InstitutionDAO; import java.io.InputStream;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SebClientConfigDAO; import java.io.OutputStream;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService; import java.io.PipedInputStream;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService; import java.io.PipedOutputStream;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService.Strategy; import java.nio.charset.StandardCharsets;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService; import java.util.Collection;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebConfigEncryptionServiceImpl.EncryptionContext; import java.util.Collections;
import ch.ethz.seb.sebserver.webservice.weblayer.oauth.WebserviceResourceConfiguration; import java.util.UUID;
@Lazy @Lazy
@Service @Service
@WebServiceProfile @WebServiceProfile
public class ClientConfigServiceImpl implements ClientConfigService { public class ClientConfigServiceImpl implements ClientConfigService {
private static final Logger log = LoggerFactory.getLogger(ClientConfigServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(ClientConfigServiceImpl.class);
private final InstitutionDAO institutionDAO; private static final String SEB_CLIENT_CONFIG_TEMPLATE_XML =
private final SebClientConfigDAO sebClientConfigDAO; " <dict>\r\n" +
private final ClientCredentialService clientCredentialService; " <key>sebMode</key>\r\n" +
private final SebConfigEncryptionService sebConfigEncryptionService; " <integer>1</integer>\r\n" +
private final PasswordEncoder clientPasswordEncoder; " <key>sebConfigPurpose</key>\r\n" +
private final ZipService zipService; " <integer>%s</integer>\r\n" +
private final TokenStore tokenStore; " <key>sebServerFallback</key>\r\n" +
private final WebserviceInfo webserviceInfo; " <%s />\r\n" +
"%s" +
protected ClientConfigServiceImpl( " <key>sebServerURL</key>\r\n" +
final InstitutionDAO institutionDAO, " <string>%s</string>\r\n" +
final SebClientConfigDAO sebClientConfigDAO, " <key>sebServerConfiguration</key>\r\n" +
final ClientCredentialService clientCredentialService, " <dict>\r\n" +
final SebConfigEncryptionService sebConfigEncryptionService, " <key>institution</key>\r\n" +
final ZipService zipService, " <string>%s</string>\r\n" +
final TokenStore tokenStore, " <key>clientName</key>\r\n" +
@Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder clientPasswordEncoder, " <string>%s</string>\r\n" +
final WebserviceInfo webserviceInfo) { " <key>clientSecret</key>\r\n" +
" <string>%s</string>\r\n" +
this.institutionDAO = institutionDAO; " <key>apiDiscovery</key>\r\n" +
this.sebClientConfigDAO = sebClientConfigDAO; " <string>%s</string>\r\n" +
this.clientCredentialService = clientCredentialService; " </dict>\r\n" +
this.sebConfigEncryptionService = sebConfigEncryptionService; " </dict>\r\n";
this.zipService = zipService;
this.clientPasswordEncoder = clientPasswordEncoder; private final static String SEB_CLIENT_CONFIG_INTEGER_TEMPLATE =
this.tokenStore = tokenStore; " <key>%s</key>\r\n" +
this.webserviceInfo = webserviceInfo; " <integer>%s</integer>\r\n";
}
private final static String SEB_CLIENT_CONFIG_STRING_TEMPLATE =
@Override " <key>%s</key>\r\n" +
public boolean hasSebClientConfigurationForInstitution(final Long institutionId) { " <string>%s</string>\r\n";
final Result<Collection<SebClientConfig>> all = this.sebClientConfigDAO.all(institutionId, true);
return all != null && !all.hasError() && !all.getOrThrow().isEmpty(); private final InstitutionDAO institutionDAO;
} private final SebClientConfigDAO sebClientConfigDAO;
private final ClientCredentialService clientCredentialService;
@Override private final SebConfigEncryptionService sebConfigEncryptionService;
public Result<SebClientConfig> autoCreateSebClientConfigurationForInstitution(final Long institutionId) { private final PasswordEncoder clientPasswordEncoder;
return Result.tryCatch(() -> { private final ZipService zipService;
final Institution institution = this.institutionDAO private final TokenStore tokenStore;
.byPK(institutionId) private final WebserviceInfo webserviceInfo;
.getOrThrow();
protected ClientConfigServiceImpl(
return new SebClientConfig( final InstitutionDAO institutionDAO,
null, final SebClientConfigDAO sebClientConfigDAO,
institutionId, final ClientCredentialService clientCredentialService,
institution.name + "_" + UUID.randomUUID(), final SebConfigEncryptionService sebConfigEncryptionService,
null, final ZipService zipService,
null, final TokenStore tokenStore,
null, @Qualifier(WebSecurityConfig.CLIENT_PASSWORD_ENCODER_BEAN_NAME) final PasswordEncoder clientPasswordEncoder,
null, final WebserviceInfo webserviceInfo) {
true);
}) this.institutionDAO = institutionDAO;
.flatMap(this.sebClientConfigDAO::createNew); this.sebClientConfigDAO = sebClientConfigDAO;
} this.clientCredentialService = clientCredentialService;
this.sebConfigEncryptionService = sebConfigEncryptionService;
@Override this.zipService = zipService;
public Result<ClientDetails> getClientConfigDetails(final String clientName) { this.clientPasswordEncoder = clientPasswordEncoder;
return this.getEncodedClientConfigSecret(clientName) this.tokenStore = tokenStore;
.map(pwd -> { this.webserviceInfo = webserviceInfo;
}
final BaseClientDetails baseClientDetails = new BaseClientDetails(
Utils.toString(clientName), @Override
WebserviceResourceConfiguration.EXAM_API_RESOURCE_ID, public boolean hasSebClientConfigurationForInstitution(final Long institutionId) {
null, final Result<Collection<SebClientConfig>> all = this.sebClientConfigDAO.all(institutionId, true);
Constants.OAUTH2_GRANT_TYPE_CLIENT_CREDENTIALS, return all != null && !all.hasError() && !all.getOrThrow().isEmpty();
StringUtils.EMPTY); }
baseClientDetails.setScope(Collections.emptySet()); @Override
baseClientDetails.setClientSecret(Utils.toString(pwd)); public Result<SebClientConfig> autoCreateSebClientConfigurationForInstitution(final Long institutionId) {
baseClientDetails.setAccessTokenValiditySeconds(-1); // not expiring return Result.tryCatch(() -> {
final Institution institution = this.institutionDAO
if (log.isDebugEnabled()) { .byPK(institutionId)
log.debug("Created new BaseClientDetails for id: {}", clientName); .getOrThrow();
}
return new SebClientConfig(
return baseClientDetails; null,
}); institutionId,
} institution.name + "_" + UUID.randomUUID(),
null,
@Override false,
public void exportSebClientConfiguration( null,
final OutputStream output, null,
final String modelId) { null,
null,
final SebClientConfig config = this.sebClientConfigDAO null,
.byModelId(modelId).getOrThrow(); null,
null,
final CharSequence encryptionPassword = this.sebClientConfigDAO null,
.getConfigPasswortCipher(config.getModelId()) null,
.getOr(null); null,
null,
final String plainTextConfig = getPlainXMLConfig(config); true);
})
PipedOutputStream pOut = null; .flatMap(this.sebClientConfigDAO::createNew);
PipedInputStream pIn = null; }
try { @Override
public Result<ClientDetails> getClientConfigDetails(final String clientName) {
// zip the plain text return this.getEncodedClientConfigSecret(clientName)
final InputStream plainIn = IOUtils.toInputStream( .map(pwd -> {
Constants.XML_VERSION_HEADER +
Constants.XML_DOCTYPE_HEADER + final BaseClientDetails baseClientDetails = new BaseClientDetails(
Constants.XML_PLIST_START_V1 + Utils.toString(clientName),
plainTextConfig + WebserviceResourceConfiguration.EXAM_API_RESOURCE_ID,
Constants.XML_PLIST_END, null,
StandardCharsets.UTF_8.name()); Constants.OAUTH2_GRANT_TYPE_CLIENT_CREDENTIALS,
StringUtils.EMPTY);
pOut = new PipedOutputStream();
pIn = new PipedInputStream(pOut); baseClientDetails.setScope(Collections.emptySet());
baseClientDetails.setClientSecret(Utils.toString(pwd));
this.zipService.write(pOut, plainIn); baseClientDetails.setAccessTokenValiditySeconds(-1); // not expiring
if (encryptionPassword != null) { if (log.isDebugEnabled()) {
passwordEncryption(output, encryptionPassword, pIn); log.debug("Created new BaseClientDetails for id: {}", clientName);
} else { }
this.sebConfigEncryptionService.streamEncrypted(
output, return baseClientDetails;
pIn, });
EncryptionContext.contextOfPlainText()); }
}
@Override
if (log.isDebugEnabled()) { public void exportSebClientConfiguration(
log.debug("*** Finished Seb client configuration download streaming composition"); final OutputStream output,
} final String modelId) {
} catch (final Exception e) { final SebClientConfig config = this.sebClientConfigDAO
log.error("Error while zip and encrypt seb client config stream: ", e); .byModelId(modelId).getOrThrow();
try {
if (pIn != null) { final CharSequence encryptionPassword = this.sebClientConfigDAO
pIn.close(); .getConfigPasswordCipher(config.getModelId())
} .getOr(null);
} catch (final IOException e1) {
log.error("Failed to close PipedInputStream: ", e1); final String plainTextXMLContent = extractXMLContent(config);
}
try { PipedOutputStream pOut = null;
if (pOut != null) { PipedInputStream pIn = null;
pOut.close();
} try {
} catch (final IOException e1) {
log.error("Failed to close PipedOutputStream: ", e1); // zip the plain text
} final InputStream plainIn = IOUtils.toInputStream(
} Constants.XML_VERSION_HEADER +
} Constants.XML_DOCTYPE_HEADER +
Constants.XML_PLIST_START_V1 +
public String getPlainXMLConfig(final SebClientConfig config) { plainTextXMLContent +
Constants.XML_PLIST_END,
final ClientCredentials sebClientCredentials = this.sebClientConfigDAO StandardCharsets.UTF_8.name());
.getSebClientCredentials(config.getModelId())
.getOrThrow(); pOut = new PipedOutputStream();
pIn = new PipedInputStream(pOut);
final CharSequence plainClientId = sebClientCredentials.clientId;
final CharSequence plainClientSecret = this.clientCredentialService this.zipService.write(pOut, plainIn);
.getPlainClientSecret(sebClientCredentials);
if (encryptionPassword != null) {
final String plainTextConfig = extractXML( passwordEncryption(output, encryptionPassword, pIn);
config, } else {
plainClientId, this.sebConfigEncryptionService.streamEncrypted(
plainClientSecret); output,
pIn,
return plainTextConfig; EncryptionContext.contextOfPlainText());
} }
private String extractXML( if (log.isDebugEnabled()) {
final SebClientConfig config, log.debug("*** Finished Seb client configuration download streaming composition");
final CharSequence plainClientId, }
final CharSequence plainClientSecret) {
} catch (final Exception e) {
final String plainTextConfig = String.format( log.error("Error while zip and encrypt seb client config stream: ", e);
SEB_CLIENT_CONFIG_EXAMPLE_XML, try {
(StringUtils.isNotBlank(config.fallbackStartURL)) if (pIn != null) {
? "true" pIn.close();
: "false", }
(StringUtils.isNotBlank(config.fallbackStartURL)) } catch (final IOException e1) {
? "<key>startURL</key>\r\n <string>" + config.fallbackStartURL + "</string>\r\n" log.error("Failed to close PipedInputStream: ", e1);
: "", }
this.webserviceInfo.getExternalServerURL(), try {
String.valueOf(config.institutionId), if (pOut != null) {
plainClientId, pOut.close();
plainClientSecret, }
this.webserviceInfo.getDiscoveryEndpoint()); } catch (final IOException e1) {
log.error("Failed to close PipedOutputStream: ", e1);
if (log.isDebugEnabled()) { }
log.debug("SEB client configuration export:\n {}", plainTextConfig); }
} }
return plainTextConfig; private String extractXMLContent(final SebClientConfig config) {
}
String fallbackAddition = "";
@Override if (BooleanUtils.isTrue(config.fallback)) {
public void flushClientConfigData(final BulkActionEvent event) { fallbackAddition += String.format(
try { SEB_CLIENT_CONFIG_STRING_TEMPLATE,
final BulkAction bulkAction = event.getBulkAction(); SebClientConfig.ATTR_FALLBACK_START_URL,
config.fallbackStartURL);
if (bulkAction.type == BulkActionType.DEACTIVATE ||
bulkAction.type == BulkActionType.HARD_DELETE) { fallbackAddition += String.format(
SEB_CLIENT_CONFIG_INTEGER_TEMPLATE,
bulkAction.extractKeys(EntityType.SEB_CLIENT_CONFIGURATION) SebClientConfig.ATTR_FALLBACK_TIMEOUT,
.stream() config.fallbackTimeout);
.forEach(this::flushClientConfigData);
} fallbackAddition += String.format(
SEB_CLIENT_CONFIG_INTEGER_TEMPLATE,
} catch (final Exception e) { SebClientConfig.ATTR_FALLBACK_ATTEMPTS,
log.error("Unexpected error while trying to flush ClientConfig data ", e); config.fallbackAttempts);
}
} fallbackAddition += String.format(
SEB_CLIENT_CONFIG_INTEGER_TEMPLATE,
private void flushClientConfigData(final EntityKey key) { SebClientConfig.ATTR_FALLBACK_ATTEMPT_INTERVAL,
try { config.fallbackAttemptInterval);
final String clientName = this.sebClientConfigDAO.getSebClientCredentials(key.modelId)
.getOrThrow() if (StringUtils.isNotBlank(config.fallbackPassword)) {
.clientIdAsString(); CharSequence decrypt = clientCredentialService.decrypt(config.fallbackPassword);
fallbackAddition += String.format(
final Collection<OAuth2AccessToken> tokensByClientId = this.tokenStore.findTokensByClientId(clientName); SEB_CLIENT_CONFIG_STRING_TEMPLATE,
tokensByClientId.stream() SebClientConfig.ATTR_FALLBACK_PASSWORD,
.forEach(token -> this.tokenStore.removeAccessToken(token)); Utils.hash_SHA_256_Base_16(decrypt));
} catch (final Exception e) { }
log.error("Unexpected error while trying to flush ClientConfig data for {}", key, e);
} if (StringUtils.isNotBlank(config.quitPassword)) {
} CharSequence decrypt = clientCredentialService.decrypt(config.quitPassword);
fallbackAddition += String.format(
private void passwordEncryption( SEB_CLIENT_CONFIG_STRING_TEMPLATE,
final OutputStream output, SebClientConfig.ATTR_QUIT_PASSWORD,
final CharSequence encryptionPassword, Utils.hash_SHA_256_Base_16(decrypt));
final InputStream input) { }
}
if (log.isDebugEnabled()) {
log.debug("*** Seb client configuration with password based encryption"); final ClientCredentials sebClientCredentials = this.sebClientConfigDAO
} .getSebClientCredentials(config.getModelId())
.getOrThrow();
final CharSequence encryptionPasswordPlaintext = this.clientCredentialService final CharSequence plainClientId = sebClientCredentials.clientId;
.decrypt(encryptionPassword); final CharSequence plainClientSecret = this.clientCredentialService
.getPlainClientSecret(sebClientCredentials);
this.sebConfigEncryptionService.streamEncrypted(
output, final String plainTextConfig = String.format(
input, SEB_CLIENT_CONFIG_TEMPLATE_XML,
EncryptionContext.contextOf( config.configPurpose.ordinal(),
Strategy.PASSWORD_PSWD, (StringUtils.isNotBlank(config.fallbackStartURL))
encryptionPasswordPlaintext)); ? "true"
} : "false",
fallbackAddition,
/** Get a encoded clientSecret for the SebClientConfiguration with specified clientId/clientName. this.webserviceInfo.getExternalServerURL(),
* config.institutionId,
* @param clientId the clientId/clientName plainClientId,
* @return encoded clientSecret for that SebClientConfiguration with clientId or null of not existing */ plainClientSecret,
private Result<CharSequence> getEncodedClientConfigSecret(final String clientId) { this.webserviceInfo.getDiscoveryEndpoint());
return this.sebClientConfigDAO.getConfigPasswortCipherByClientName(clientId)
.map(cipher -> this.clientPasswordEncoder.encode(this.clientCredentialService.decrypt(cipher))); if (log.isDebugEnabled()) {
} log.debug("SEB client configuration export:\n {}", plainTextConfig);
}
}
return plainTextConfig;
}
@Override
public void flushClientConfigData(final BulkActionEvent event) {
try {
final BulkAction bulkAction = event.getBulkAction();
if (bulkAction.type == BulkActionType.DEACTIVATE ||
bulkAction.type == BulkActionType.HARD_DELETE) {
bulkAction.extractKeys(EntityType.SEB_CLIENT_CONFIGURATION)
.forEach(this::flushClientConfigData);
}
} catch (final Exception e) {
log.error("Unexpected error while trying to flush ClientConfig data ", e);
}
}
private void flushClientConfigData(final EntityKey key) {
try {
final String clientName = this.sebClientConfigDAO.getSebClientCredentials(key.modelId)
.getOrThrow()
.clientIdAsString();
final Collection<OAuth2AccessToken> tokensByClientId = this.tokenStore.findTokensByClientId(clientName);
tokensByClientId.forEach(this.tokenStore::removeAccessToken);
} catch (final Exception e) {
log.error("Unexpected error while trying to flush ClientConfig data for {}", key, e);
}
}
private void passwordEncryption(
final OutputStream output,
final CharSequence encryptionPassword,
final InputStream input) {
if (log.isDebugEnabled()) {
log.debug("*** Seb client configuration with password based encryption");
}
final CharSequence encryptionPasswordPlaintext = this.clientCredentialService
.decrypt(encryptionPassword);
this.sebConfigEncryptionService.streamEncrypted(
output,
input,
EncryptionContext.contextOf(
Strategy.PASSWORD_PSWD,
encryptionPasswordPlaintext));
}
/** Get a encoded clientSecret for the SebClientConfiguration with specified clientId/clientName.
*
* @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.getConfigPasswordCipherByClientName(clientId)
.map(cipher -> this.clientPasswordEncoder.encode(this.clientCredentialService.decrypt(cipher)));
}
}

View file

@ -1,313 +1,318 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PipedInputStream; import java.io.PipedInputStream;
import java.io.PipedOutputStream; import java.io.PipedOutputStream;
import java.io.SequenceInputStream; import java.io.SequenceInputStream;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.SAXParserFactory;
import org.apache.tomcat.util.http.fileupload.IOUtils; import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.slf4j.Logger; import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.LoggerFactory; import org.slf4j.Logger;
import org.springframework.context.annotation.Lazy; import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.scheduling.annotation.Async;
import org.xml.sax.SAXException; import org.springframework.stereotype.Component;
import org.xml.sax.SAXException;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; import ch.ethz.seb.sebserver.gbl.async.AsyncServiceSpringConfig;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverter; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverterService; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverter;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationFormat; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverterService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationFormat;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService;
@Lazy
@Component @Lazy
@WebServiceProfile @Component
public class ExamConfigIO { @WebServiceProfile
public class ExamConfigIO {
private static final Logger log = LoggerFactory.getLogger(ExamConfigIO.class);
private static final Logger log = LoggerFactory.getLogger(ExamConfigIO.class);
private static final byte[] XML_VERSION_HEADER_UTF_8 = Utils.toByteArray(Constants.XML_VERSION_HEADER);
private static final byte[] XML_DOCTYPE_HEADER_UTF_8 = Utils.toByteArray(Constants.XML_DOCTYPE_HEADER); private static final byte[] XML_VERSION_HEADER_UTF_8 = Utils.toByteArray(Constants.XML_VERSION_HEADER);
private static final byte[] XML_PLIST_START_V1_UTF_8 = Utils.toByteArray(Constants.XML_PLIST_START_V1); private static final byte[] XML_DOCTYPE_HEADER_UTF_8 = Utils.toByteArray(Constants.XML_DOCTYPE_HEADER);
private static final byte[] XML_PLIST_END_UTF_8 = Utils.toByteArray(Constants.XML_PLIST_END); private static final byte[] XML_PLIST_START_V1_UTF_8 = Utils.toByteArray(Constants.XML_PLIST_START_V1);
private static final byte[] XML_DICT_START_UTF_8 = Utils.toByteArray(Constants.XML_DICT_START); private static final byte[] XML_PLIST_END_UTF_8 = Utils.toByteArray(Constants.XML_PLIST_END);
private static final byte[] XML_DICT_END_UTF_8 = Utils.toByteArray(Constants.XML_DICT_END); private static final byte[] XML_DICT_START_UTF_8 = Utils.toByteArray(Constants.XML_DICT_START);
private static final byte[] JSON_START = Utils.toByteArray("{"); private static final byte[] XML_DICT_END_UTF_8 = Utils.toByteArray(Constants.XML_DICT_END);
private static final byte[] JSON_END = Utils.toByteArray("}"); private static final byte[] JSON_START = Utils.toByteArray("{");
private static final byte[] JSON_SEPARATOR = Utils.toByteArray(Constants.LIST_SEPARATOR); private static final byte[] JSON_END = Utils.toByteArray("}");
private static final byte[] JSON_SEPARATOR = Utils.toByteArray(Constants.LIST_SEPARATOR);
private final ConfigurationAttributeDAO configurationAttributeDAO;
private final ConfigurationValueDAO configurationValueDAO; private final ConfigurationAttributeDAO configurationAttributeDAO;
private final ConfigurationDAO configurationDAO; private final ConfigurationValueDAO configurationValueDAO;
private final AttributeValueConverterService attributeValueConverterService; private final ConfigurationDAO configurationDAO;
private final ZipService zipService; private final AttributeValueConverterService attributeValueConverterService;
private final ZipService zipService;
protected ExamConfigIO( private final Cryptor cryptor;
final ConfigurationAttributeDAO configurationAttributeDAO,
final ConfigurationValueDAO configurationValueDAO, protected ExamConfigIO(
final ConfigurationDAO configurationDAO, final ConfigurationAttributeDAO configurationAttributeDAO,
final AttributeValueConverterService attributeValueConverterService, final ConfigurationValueDAO configurationValueDAO,
final ZipService zipService) { final ConfigurationDAO configurationDAO,
final AttributeValueConverterService attributeValueConverterService,
this.configurationAttributeDAO = configurationAttributeDAO; final ZipService zipService,
this.configurationValueDAO = configurationValueDAO; final Cryptor cryptor) {
this.configurationDAO = configurationDAO;
this.attributeValueConverterService = attributeValueConverterService; this.configurationAttributeDAO = configurationAttributeDAO;
this.zipService = zipService; this.configurationValueDAO = configurationValueDAO;
} this.configurationDAO = configurationDAO;
this.attributeValueConverterService = attributeValueConverterService;
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME) this.zipService = zipService;
void exportPlain( this.cryptor = cryptor;
final ConfigurationFormat exportFormat, }
final OutputStream out,
final Long institutionId, @Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
final Long configurationNodeId) throws Exception { void exportPlain(
final ConfigurationFormat exportFormat,
if (log.isDebugEnabled()) { final OutputStream out,
log.debug("Start export SEB plain XML configuration asynconously"); final Long institutionId,
} final Long configurationNodeId) throws Exception {
try { if (log.isDebugEnabled()) {
log.debug("Start export SEB plain XML configuration asynconously");
// get all defined root configuration attributes prepared and sorted }
final List<ConfigurationAttribute> sortedAttributes = this.configurationAttributeDAO.getAllRootAttributes()
.getOrThrow() try {
.stream()
.flatMap(this::convertAttribute) // get all defined root configuration attributes prepared and sorted
.filter(exportFormatBasedAttributeFilter(exportFormat)) final List<ConfigurationAttribute> sortedAttributes = this.configurationAttributeDAO.getAllRootAttributes()
.sorted() .getOrThrow()
.collect(Collectors.toList()); .stream()
.flatMap(this::convertAttribute)
// get follow-up configurationId for given configurationNodeId .filter(exportFormatBasedAttributeFilter(exportFormat))
final Long configurationId = this.configurationDAO .sorted()
.getConfigurationLastStableVersion(configurationNodeId) .collect(Collectors.toList());
.getOrThrow().id;
// get follow-up configurationId for given configurationNodeId
final Function<ConfigurationAttribute, ConfigurationValue> configurationValueSupplier = final Long configurationId = this.configurationDAO
getConfigurationValueSupplier(institutionId, configurationId); .getConfigurationLastStableVersion(configurationNodeId)
.getOrThrow().id;
writeHeader(exportFormat, out);
final Function<ConfigurationAttribute, ConfigurationValue> configurationValueSupplier =
// write attributes getConfigurationValueSupplier(institutionId, configurationId);
final Iterator<ConfigurationAttribute> iterator = sortedAttributes.iterator();
while (iterator.hasNext()) { writeHeader(exportFormat, out);
final ConfigurationAttribute attribute = iterator.next(); // write attributes
final AttributeValueConverter attributeValueConverter = final Iterator<ConfigurationAttribute> iterator = sortedAttributes.iterator();
this.attributeValueConverterService.getAttributeValueConverter(attribute); while (iterator.hasNext()) {
switch (exportFormat) { final ConfigurationAttribute attribute = iterator.next();
case XML: { final AttributeValueConverter attributeValueConverter =
attributeValueConverter.convertToXML( this.attributeValueConverterService.getAttributeValueConverter(attribute);
out,
attribute, switch (exportFormat) {
configurationValueSupplier); case XML: {
break; attributeValueConverter.convertToXML(
} out,
case JSON: { attribute,
attributeValueConverter.convertToJSON( configurationValueSupplier);
out, break;
attribute, }
configurationValueSupplier); case JSON: {
if (iterator.hasNext()) { attributeValueConverter.convertToJSON(
out.write(JSON_SEPARATOR); out,
} attribute,
break; configurationValueSupplier);
} if (iterator.hasNext()) {
} out.write(JSON_SEPARATOR);
} }
break;
writeFooter(exportFormat, out); }
}
if (log.isDebugEnabled()) { }
log.debug("Finished export SEB plain XML configuration asynconously");
} writeFooter(exportFormat, out);
} catch (final Exception e) { if (log.isDebugEnabled()) {
log.error("Unexpected error while trying to write SEB Exam Configuration XML to output stream: ", e); log.debug("Finished export SEB plain XML configuration asynconously");
throw e; }
} finally {
try { } catch (final Exception e) {
out.flush(); log.error("Unexpected error while trying to write SEB Exam Configuration XML to output stream: ", e);
} catch (final IOException e1) { throw e;
log.error("Unable to flush output stream after error"); } finally {
} try {
IOUtils.closeQuietly(out); out.flush();
} } catch (final IOException e1) {
} log.error("Unable to flush output stream after error");
}
/** This parses the XML from given InputStream with a SAX parser to avoid keeping the IOUtils.closeQuietly(out);
* whole XML file in memory and keep up with the streaming approach of SEB Exam Configuration }
* to avoid trouble with big SEB Exam Configuration in the future. }
*
* @param in The InputString to constantly read the XML from /** This parses the XML from given InputStream with a SAX parser to avoid keeping the
* @param institutionId the institionId of the import * whole XML file in memory and keep up with the streaming approach of SEB Exam Configuration
* @param configurationId the identifier of the internal configuration to apply the imported values to */ * to avoid trouble with big SEB Exam Configuration in the future.
void importPlainXML(final InputStream in, final Long institutionId, final Long configurationId) { *
try { * @param in The InputString to constantly read the XML from
// get all attributes and map the names to ids * @param institutionId the institionId of the import
final Map<String, ConfigurationAttribute> attributeMap = this.configurationAttributeDAO * @param configurationId the identifier of the internal configuration to apply the imported values to */
.allMatching(new FilterMap()) void importPlainXML(final InputStream in, final Long institutionId, final Long configurationId) {
.getOrThrow() try {
.stream() // get all attributes and map the names to ids
.collect(Collectors.toMap( final Map<String, ConfigurationAttribute> attributeMap = this.configurationAttributeDAO
attr -> attr.name, .allMatching(new FilterMap())
Function.identity())); .getOrThrow()
.stream()
// the SAX handler with a ConfigValue sink that saves the values to DB .collect(Collectors.toMap(
// and a attribute-name/id mapping function with pre-created mapping attr -> attr.name,
final ExamConfigXMLParser examConfigImportHandler = new ExamConfigXMLParser( Function.identity()));
institutionId,
configurationId, // the SAX handler with a ConfigValue sink that saves the values to DB
value -> this.configurationValueDAO // and a attribute-name/id mapping function with pre-created mapping
.save(value) final ExamConfigXMLParser examConfigImportHandler = new ExamConfigXMLParser(
.getOrThrow(), cryptor,
attributeMap::get); institutionId,
configurationId,
// SAX parsing value -> this.configurationValueDAO
final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); .save(value)
final SAXParser parser = saxParserFactory.newSAXParser(); .getOrThrow(),
parser.parse(in, examConfigImportHandler); attributeMap::get);
} catch (final ParserConfigurationException e) { // SAX parsing
log.error("Unexpected error while trying to parse imported SEB Config XML: ", e); final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
throw new RuntimeException(e); final SAXParser parser = saxParserFactory.newSAXParser();
} catch (final SAXException e) { parser.parse(in, examConfigImportHandler);
log.error("Unexpected error while trying to parse imported SEB Config XML: ", e);
throw new RuntimeException(e); } catch (final ParserConfigurationException e) {
} catch (final IOException e) { log.error("Unexpected error while trying to parse imported SEB Config XML: ", e);
log.error("Unexpected error while trying to parse imported SEB Config XML: ", e); throw new RuntimeException(e);
throw new RuntimeException(e); } catch (final SAXException e) {
} finally { log.error("Unexpected error while trying to parse imported SEB Config XML: ", e);
IOUtils.closeQuietly(in); throw new RuntimeException(e);
} } catch (final IOException e) {
} log.error("Unexpected error while trying to parse imported SEB Config XML: ", e);
throw new RuntimeException(e);
InputStream unzip(final InputStream input) throws Exception { } finally {
IOUtils.closeQuietly(in);
final byte[] zipHeader = new byte[Constants.GZIP_HEADER_LENGTH]; }
final int read = input.read(zipHeader); }
if (read < Constants.GZIP_HEADER_LENGTH) {
throw new IllegalArgumentException("Failed to verify Zip type from input stream. Header size mismatch."); InputStream unzip(final InputStream input) throws Exception {
}
final byte[] zipHeader = new byte[Constants.GZIP_HEADER_LENGTH];
final boolean isZipped = Byte.toUnsignedInt(zipHeader[0]) == Constants.GZIP_ID1 final int read = input.read(zipHeader);
&& Byte.toUnsignedInt(zipHeader[1]) == Constants.GZIP_ID2 if (read < Constants.GZIP_HEADER_LENGTH) {
&& Byte.toUnsignedInt(zipHeader[2]) == Constants.GZIP_CM; throw new IllegalArgumentException("Failed to verify Zip type from input stream. Header size mismatch.");
}
final InputStream sequencedInput = new SequenceInputStream(
new ByteArrayInputStream(zipHeader, 0, Constants.GZIP_HEADER_LENGTH), final boolean isZipped = Byte.toUnsignedInt(zipHeader[0]) == Constants.GZIP_ID1
input); && Byte.toUnsignedInt(zipHeader[1]) == Constants.GZIP_ID2
&& Byte.toUnsignedInt(zipHeader[2]) == Constants.GZIP_CM;
if (isZipped) {
final InputStream sequencedInput = new SequenceInputStream(
final PipedInputStream pipedIn = new PipedInputStream(); new ByteArrayInputStream(zipHeader, 0, Constants.GZIP_HEADER_LENGTH),
final PipedOutputStream pipedOut = new PipedOutputStream(pipedIn); input);
this.zipService.read(pipedOut, sequencedInput);
if (isZipped) {
return pipedIn;
} else { final PipedInputStream pipedIn = new PipedInputStream();
return sequencedInput; final PipedOutputStream pipedOut = new PipedOutputStream(pipedIn);
} this.zipService.read(pipedOut, sequencedInput);
}
return pipedIn;
private Predicate<ConfigurationAttribute> exportFormatBasedAttributeFilter(final ConfigurationFormat format) { } else {
// Filter originatorVersion according to: https://www.safeexambrowser.org/developer/seb-config-key.html return sequencedInput;
return attr -> !("originatorVersion".equals(attr.getName()) && format == ConfigurationFormat.JSON); }
} }
private void writeFooter( private Predicate<ConfigurationAttribute> exportFormatBasedAttributeFilter(final ConfigurationFormat format) {
final ConfigurationFormat exportFormat, // Filter originatorVersion according to: https://www.safeexambrowser.org/developer/seb-config-key.html
final OutputStream out) throws IOException { return attr -> !("originatorVersion".equals(attr.getName()) && format == ConfigurationFormat.JSON);
}
if (exportFormat == ConfigurationFormat.XML) {
// plist close private void writeFooter(
out.write(XML_DICT_END_UTF_8); final ConfigurationFormat exportFormat,
out.write(XML_PLIST_END_UTF_8); final OutputStream out) throws IOException {
} else {
out.write(JSON_END); if (exportFormat == ConfigurationFormat.XML) {
} // plist close
} out.write(XML_DICT_END_UTF_8);
out.write(XML_PLIST_END_UTF_8);
private void writeHeader( } else {
final ConfigurationFormat exportFormat, out.write(JSON_END);
final OutputStream out) throws IOException { }
}
if (exportFormat == ConfigurationFormat.XML) {
writeXMLHeaderInformation(out); private void writeHeader(
} else { final ConfigurationFormat exportFormat,
writeJSONHeaderInformation(out); final OutputStream out) throws IOException {
}
} if (exportFormat == ConfigurationFormat.XML) {
writeXMLHeaderInformation(out);
private void writeJSONHeaderInformation(final OutputStream out) throws IOException { } else {
out.write(JSON_START); writeJSONHeaderInformation(out);
} }
}
private void writeXMLHeaderInformation(final OutputStream out) throws IOException {
// write headers private void writeJSONHeaderInformation(final OutputStream out) throws IOException {
out.write(XML_VERSION_HEADER_UTF_8); out.write(JSON_START);
out.write(XML_DOCTYPE_HEADER_UTF_8); }
// plist open private void writeXMLHeaderInformation(final OutputStream out) throws IOException {
out.write(XML_PLIST_START_V1_UTF_8); // write headers
out.write(XML_DICT_START_UTF_8); out.write(XML_VERSION_HEADER_UTF_8);
} out.write(XML_DOCTYPE_HEADER_UTF_8);
private Stream<ConfigurationAttribute> convertAttribute(final ConfigurationAttribute attr) { // plist open
final AttributeValueConverter attributeValueConverter = out.write(XML_PLIST_START_V1_UTF_8);
this.attributeValueConverterService.getAttributeValueConverter(attr); out.write(XML_DICT_START_UTF_8);
if (attributeValueConverter != null) { }
return attributeValueConverter.convertAttribute(attr);
} else { private Stream<ConfigurationAttribute> convertAttribute(final ConfigurationAttribute attr) {
return Stream.of(attr); final AttributeValueConverter attributeValueConverter =
} this.attributeValueConverterService.getAttributeValueConverter(attr);
} if (attributeValueConverter != null) {
return attributeValueConverter.convertAttribute(attr);
private Function<ConfigurationAttribute, ConfigurationValue> getConfigurationValueSupplier( } else {
final Long institutionId, return Stream.of(attr);
final Long configurationId) { }
}
final Map<Long, ConfigurationValue> mapping = this.configurationValueDAO
.allRootAttributeValues(institutionId, configurationId) private Function<ConfigurationAttribute, ConfigurationValue> getConfigurationValueSupplier(
.getOrThrow() final Long institutionId,
.stream() final Long configurationId) {
.collect(Collectors.toMap(
ConfigurationValue::getAttributeId, final Map<Long, ConfigurationValue> mapping = this.configurationValueDAO
Function.identity())); .allRootAttributeValues(institutionId, configurationId)
.getOrThrow()
return attr -> mapping.get(attr.id); .stream()
} .collect(Collectors.toMap(
ConfigurationValue::getAttributeId,
} Function.identity()));
return attr -> mapping.get(attr.id);
}
}

View file

@ -1,432 +1,432 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PipedInputStream; import java.io.PipedInputStream;
import java.io.PipedOutputStream; import java.io.PipedOutputStream;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException; import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException; import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration; import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationFormat; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationFormat;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationValueValidator; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ConfigurationValueValidator;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService.Strategy; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.SebConfigEncryptionService.Strategy;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ZipService; 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.servicelayer.sebconfig.impl.SebConfigEncryptionServiceImpl.EncryptionContext;
@Lazy @Lazy
@Service @Service
@WebServiceProfile @WebServiceProfile
public class ExamConfigServiceImpl implements ExamConfigService { public class ExamConfigServiceImpl implements ExamConfigService {
private static final Logger log = LoggerFactory.getLogger(ExamConfigServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(ExamConfigServiceImpl.class);
private final ExamConfigIO examConfigIO; private final ExamConfigIO examConfigIO;
private final ConfigurationAttributeDAO configurationAttributeDAO; private final ConfigurationAttributeDAO configurationAttributeDAO;
private final ExamConfigurationMapDAO examConfigurationMapDAO; private final ExamConfigurationMapDAO examConfigurationMapDAO;
private final Collection<ConfigurationValueValidator> validators; private final Collection<ConfigurationValueValidator> validators;
private final ClientCredentialService clientCredentialService; private final ClientCredentialService clientCredentialService;
private final ZipService zipService; private final ZipService zipService;
private final SebConfigEncryptionService sebConfigEncryptionService; private final SebConfigEncryptionService sebConfigEncryptionService;
protected ExamConfigServiceImpl( protected ExamConfigServiceImpl(
final ExamConfigIO examConfigIO, final ExamConfigIO examConfigIO,
final ConfigurationAttributeDAO configurationAttributeDAO, final ConfigurationAttributeDAO configurationAttributeDAO,
final ExamConfigurationMapDAO examConfigurationMapDAO, final ExamConfigurationMapDAO examConfigurationMapDAO,
final Collection<ConfigurationValueValidator> validators, final Collection<ConfigurationValueValidator> validators,
final ClientCredentialService clientCredentialService, final ClientCredentialService clientCredentialService,
final ZipService zipService, final ZipService zipService,
final SebConfigEncryptionService sebConfigEncryptionService) { final SebConfigEncryptionService sebConfigEncryptionService) {
this.examConfigIO = examConfigIO; this.examConfigIO = examConfigIO;
this.configurationAttributeDAO = configurationAttributeDAO; this.configurationAttributeDAO = configurationAttributeDAO;
this.examConfigurationMapDAO = examConfigurationMapDAO; this.examConfigurationMapDAO = examConfigurationMapDAO;
this.validators = validators; this.validators = validators;
this.clientCredentialService = clientCredentialService; this.clientCredentialService = clientCredentialService;
this.zipService = zipService; this.zipService = zipService;
this.sebConfigEncryptionService = sebConfigEncryptionService; this.sebConfigEncryptionService = sebConfigEncryptionService;
} }
@Override @Override
public void validate(final ConfigurationValue value) throws FieldValidationException { public void validate(final ConfigurationValue value) throws FieldValidationException {
if (value == null) { if (value == null) {
log.warn("Validate called with null reference. Ignore this and skip validation"); log.warn("Validate called with null reference. Ignore this and skip validation");
return; return;
} }
final ConfigurationAttribute attribute = this.configurationAttributeDAO final ConfigurationAttribute attribute = this.configurationAttributeDAO
.byPK(value.attributeId) .byPK(value.attributeId)
.getOrThrow(); .getOrThrow();
this.validators this.validators
.stream() .stream()
.filter(validator -> !validator.validate(value, attribute)) .filter(validator -> !validator.validate(value, attribute))
.findFirst() .findFirst()
.ifPresent(validator -> validator.throwValidationError(value, attribute)); .ifPresent(validator -> validator.throwValidationError(value, attribute));
} }
@Override @Override
public void validate(final ConfigurationTableValues tableValue) throws FieldValidationException { public void validate(final ConfigurationTableValues tableValue) throws FieldValidationException {
final List<APIMessage> errors = tableValue.values.stream() final List<APIMessage> errors = tableValue.values.stream()
.map(tv -> new ConfigurationValue( .map(tv -> new ConfigurationValue(
null, null,
tableValue.institutionId, tableValue.institutionId,
tableValue.configurationId, tableValue.configurationId,
tv.attributeId, tv.attributeId,
tv.listIndex, tv.listIndex,
tv.value)) tv.value))
.flatMap(cv -> { .flatMap(cv -> {
try { try {
validate(cv); validate(cv);
return Stream.empty(); return Stream.empty();
} catch (final FieldValidationException fve) { } catch (final FieldValidationException fve) {
return Stream.of(fve); return Stream.of(fve);
} }
}) })
.map(fve -> fve.apiMessage) .map(fve -> fve.apiMessage)
.collect(Collectors.toList()); .collect(Collectors.toList());
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
throw new APIMessageException(errors); throw new APIMessageException(errors);
} }
} }
@Override @Override
public void exportPlainXML( public void exportPlainXML(
final OutputStream out, final OutputStream out,
final Long institutionId, final Long institutionId,
final Long configurationNodeId) { final Long configurationNodeId) {
this.exportPlainOnly(ConfigurationFormat.XML, out, institutionId, configurationNodeId); this.exportPlainOnly(ConfigurationFormat.XML, out, institutionId, configurationNodeId);
} }
@Override @Override
public void exportPlainJSON( public void exportPlainJSON(
final OutputStream out, final OutputStream out,
final Long institutionId, final Long institutionId,
final Long configurationNodeId) { final Long configurationNodeId) {
this.exportPlainOnly(ConfigurationFormat.JSON, out, institutionId, configurationNodeId); this.exportPlainOnly(ConfigurationFormat.JSON, out, institutionId, configurationNodeId);
} }
public Result<Long> getDefaultConfigurationIdForExam(final Long examId) { public Result<Long> getDefaultConfigurationIdForExam(final Long examId) {
return this.examConfigurationMapDAO.getDefaultConfigurationNode(examId); return this.examConfigurationMapDAO.getDefaultConfigurationNode(examId);
} }
public Result<Long> getUserConfigurationIdForExam(final Long examId, final String userId) { public Result<Long> getUserConfigurationIdForExam(final Long examId, final String userId) {
return this.examConfigurationMapDAO.getUserConfigurationNodeId(examId, userId); return this.examConfigurationMapDAO.getUserConfigurationNodeId(examId, userId);
} }
@Override @Override
public Long exportForExam( public Long exportForExam(
final OutputStream out, final OutputStream out,
final Long institutionId, final Long institutionId,
final Long examId, final Long examId,
final String userId) { final String userId) {
final Long configurationNodeId = (StringUtils.isBlank(userId)) final Long configurationNodeId = (StringUtils.isBlank(userId))
? getDefaultConfigurationIdForExam(examId) ? getDefaultConfigurationIdForExam(examId)
.getOrThrow() .getOrThrow()
: getUserConfigurationIdForExam(examId, userId) : getUserConfigurationIdForExam(examId, userId)
.getOrThrow(); .getOrThrow();
return exportForExam(out, institutionId, examId, configurationNodeId); return exportForExam(out, institutionId, examId, configurationNodeId);
} }
@Override @Override
public Long exportForExam( public Long exportForExam(
final OutputStream out, final OutputStream out,
final Long institutionId, final Long institutionId,
final Long examId, final Long examId,
final Long configurationNodeId) { final Long configurationNodeId) {
final CharSequence passwordCipher = this.examConfigurationMapDAO final CharSequence passwordCipher = this.examConfigurationMapDAO
.getConfigPasswortCipher(examId, configurationNodeId) .getConfigPasswordCipher(examId, configurationNodeId)
.getOr(null); .getOr(null);
if (StringUtils.isNotBlank(passwordCipher)) { if (StringUtils.isNotBlank(passwordCipher)) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("*** Seb exam configuration with password based encryption"); log.debug("*** Seb exam configuration with password based encryption");
} }
final CharSequence encryptionPasswordPlaintext = this.clientCredentialService final CharSequence encryptionPasswordPlaintext = this.clientCredentialService
.decrypt(passwordCipher); .decrypt(passwordCipher);
PipedOutputStream plainOut = null; PipedOutputStream plainOut = null;
PipedInputStream zipIn = null; PipedInputStream zipIn = null;
PipedOutputStream zipOut = null; PipedOutputStream zipOut = null;
PipedInputStream cryptIn = null; PipedInputStream cryptIn = null;
PipedOutputStream cryptOut = null; PipedOutputStream cryptOut = null;
PipedInputStream in = null; PipedInputStream in = null;
try { try {
plainOut = new PipedOutputStream(); plainOut = new PipedOutputStream();
zipIn = new PipedInputStream(plainOut); zipIn = new PipedInputStream(plainOut);
zipOut = new PipedOutputStream(); zipOut = new PipedOutputStream();
cryptIn = new PipedInputStream(zipOut); cryptIn = new PipedInputStream(zipOut);
cryptOut = new PipedOutputStream(); cryptOut = new PipedOutputStream();
in = new PipedInputStream(cryptOut); in = new PipedInputStream(cryptOut);
// streaming... // streaming...
// export plain text // export plain text
this.examConfigIO.exportPlain( this.examConfigIO.exportPlain(
ConfigurationFormat.XML, ConfigurationFormat.XML,
plainOut, plainOut,
institutionId, institutionId,
configurationNodeId); configurationNodeId);
// zip the plain text // zip the plain text
this.zipService.write(zipOut, zipIn); this.zipService.write(zipOut, zipIn);
// encrypt the zipped plain text // encrypt the zipped plain text
this.sebConfigEncryptionService.streamEncrypted( this.sebConfigEncryptionService.streamEncrypted(
cryptOut, cryptOut,
cryptIn, cryptIn,
EncryptionContext.contextOf( EncryptionContext.contextOf(
Strategy.PASSWORD_PSWD, Strategy.PASSWORD_PSWD,
encryptionPasswordPlaintext)); encryptionPasswordPlaintext));
// copy to output // copy to output
IOUtils.copyLarge(in, out); IOUtils.copyLarge(in, out);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Error while zip and encrypt seb exam config stream: ", e); log.error("Error while zip and encrypt seb exam config stream: ", e);
} finally { } finally {
IOUtils.closeQuietly(zipIn); IOUtils.closeQuietly(zipIn);
IOUtils.closeQuietly(plainOut); IOUtils.closeQuietly(plainOut);
IOUtils.closeQuietly(cryptIn); IOUtils.closeQuietly(cryptIn);
IOUtils.closeQuietly(zipOut); IOUtils.closeQuietly(zipOut);
IOUtils.closeQuietly(in); IOUtils.closeQuietly(in);
IOUtils.closeQuietly(cryptOut); IOUtils.closeQuietly(cryptOut);
} }
} else { } else {
// just export in plain text XML format // just export in plain text XML format
this.exportPlainXML(out, institutionId, configurationNodeId); this.exportPlainXML(out, institutionId, configurationNodeId);
} }
return configurationNodeId; return configurationNodeId;
} }
@Override @Override
public Result<String> generateConfigKey( public Result<String> generateConfigKey(
final Long institutionId, final Long institutionId,
final Long configurationNodeId) { final Long configurationNodeId) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Start to stream plain JSON SEB Configuration data for Config-Key generation"); log.debug("Start to stream plain JSON SEB Configuration data for Config-Key generation");
} }
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
PipedOutputStream pout = null; PipedOutputStream pout = null;
PipedInputStream pin = null; PipedInputStream pin = null;
try { try {
pout = new PipedOutputStream(); pout = new PipedOutputStream();
pin = new PipedInputStream(pout); pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain( this.examConfigIO.exportPlain(
ConfigurationFormat.JSON, ConfigurationFormat.JSON,
pout, pout,
institutionId, institutionId,
configurationNodeId); configurationNodeId);
final String json = IOUtils.toString(pin, "UTF-8"); final String json = IOUtils.toString(pin, "UTF-8");
log.trace("SEB Configuration JSON to create Config-Key: {}", json); log.trace("SEB Configuration JSON to create Config-Key: {}", json);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to trace SEB Configuration JSON: ", e); log.error("Failed to trace SEB Configuration JSON: ", e);
} }
} }
PipedOutputStream pout = null; PipedOutputStream pout = null;
PipedInputStream pin = null; PipedInputStream pin = null;
try { try {
pout = new PipedOutputStream(); pout = new PipedOutputStream();
pin = new PipedInputStream(pout); pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain( this.examConfigIO.exportPlain(
ConfigurationFormat.JSON, ConfigurationFormat.JSON,
pout, pout,
institutionId, institutionId,
configurationNodeId); configurationNodeId);
final String configKey = DigestUtils.sha256Hex(pin); final String configKey = DigestUtils.sha256Hex(pin);
return Result.of(configKey); return Result.of(configKey);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Error while stream plain JSON SEB Configuration data for Config-Key generation: ", e); log.error("Error while stream plain JSON SEB Configuration data for Config-Key generation: ", e);
return Result.ofError(e); return Result.ofError(e);
} finally { } finally {
try { try {
if (pin != null) { if (pin != null) {
pin.close(); pin.close();
} }
} catch (final IOException e1) { } catch (final IOException e1) {
log.error("Failed to close PipedInputStream: ", e1); log.error("Failed to close PipedInputStream: ", e1);
} }
try { try {
if (pout != null) { if (pout != null) {
pout.close(); pout.close();
} }
} catch (final IOException e1) { } catch (final IOException e1) {
log.error("Failed to close PipedOutputStream: ", e1); log.error("Failed to close PipedOutputStream: ", e1);
} }
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Finished to stream plain JSON SEB Configuration data for Config-Key generation"); log.debug("Finished to stream plain JSON SEB Configuration data for Config-Key generation");
} }
} }
} }
@Override @Override
public Result<Collection<String>> generateConfigKeys(final Long institutionId, final Long examId) { public Result<Collection<String>> generateConfigKeys(final Long institutionId, final Long examId) {
return this.examConfigurationMapDAO.getConfigurationNodeIds(examId) return this.examConfigurationMapDAO.getConfigurationNodeIds(examId)
.map(ids -> ids .map(ids -> ids
.stream() .stream()
.map(id -> generateConfigKey(institutionId, id) .map(id -> generateConfigKey(institutionId, id)
.getOrThrow()) .getOrThrow())
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
@Override @Override
public Result<Configuration> importFromSEBFile( public Result<Configuration> importFromSEBFile(
final Configuration config, final Configuration config,
final InputStream input, final InputStream input,
final CharSequence password) { final CharSequence password) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
Future<Exception> streamDecrypted = null; Future<Exception> streamDecrypted = null;
InputStream cryptIn = null; InputStream cryptIn = null;
PipedInputStream plainIn = null; PipedInputStream plainIn = null;
PipedOutputStream cryptOut = null; PipedOutputStream cryptOut = null;
InputStream unzippedIn = null; InputStream unzippedIn = null;
try { try {
cryptIn = this.examConfigIO.unzip(input); cryptIn = this.examConfigIO.unzip(input);
plainIn = new PipedInputStream(); plainIn = new PipedInputStream();
cryptOut = new PipedOutputStream(plainIn); cryptOut = new PipedOutputStream(plainIn);
// decrypt // decrypt
streamDecrypted = this.sebConfigEncryptionService.streamDecrypted( streamDecrypted = this.sebConfigEncryptionService.streamDecrypted(
cryptOut, cryptOut,
cryptIn, cryptIn,
EncryptionContext.contextOf(password)); EncryptionContext.contextOf(password));
// if zipped, unzip attach unzip stream first // if zipped, unzip attach unzip stream first
unzippedIn = this.examConfigIO.unzip(plainIn); unzippedIn = this.examConfigIO.unzip(plainIn);
// parse XML and import // parse XML and import
this.examConfigIO.importPlainXML( this.examConfigIO.importPlainXML(
unzippedIn, unzippedIn,
config.institutionId, config.institutionId,
config.id); config.id);
return config; return config;
} catch (final Exception e) { } catch (final Exception e) {
log.error("Unexpected error while trying to import SEB Exam Configuration: ", e); log.error("Unexpected error while trying to import SEB Exam Configuration: ", e);
if (streamDecrypted != null) { if (streamDecrypted != null) {
final Exception exception = streamDecrypted.get(); final Exception exception = streamDecrypted.get();
if (exception != null && exception instanceof APIMessageException) { if (exception != null && exception instanceof APIMessageException) {
throw exception; throw exception;
} }
} }
throw new RuntimeException("Failed to import SEB configuration. Cause is: " + e.getMessage()); throw new RuntimeException("Failed to import SEB configuration. Cause is: " + e.getMessage());
} finally { } finally {
IOUtils.closeQuietly(cryptIn); IOUtils.closeQuietly(cryptIn);
IOUtils.closeQuietly(plainIn); IOUtils.closeQuietly(plainIn);
IOUtils.closeQuietly(cryptOut); IOUtils.closeQuietly(cryptOut);
IOUtils.closeQuietly(unzippedIn); IOUtils.closeQuietly(unzippedIn);
} }
}); });
} }
private void exportPlainOnly( private void exportPlainOnly(
final ConfigurationFormat exportFormat, final ConfigurationFormat exportFormat,
final OutputStream out, final OutputStream out,
final Long institutionId, final Long institutionId,
final Long configurationNodeId) { final Long configurationNodeId) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Start to stream plain text SEB Configuration data"); log.debug("Start to stream plain text SEB Configuration data");
} }
PipedOutputStream pout = null; PipedOutputStream pout = null;
PipedInputStream pin = null; PipedInputStream pin = null;
try { try {
pout = new PipedOutputStream(); pout = new PipedOutputStream();
pin = new PipedInputStream(pout); pin = new PipedInputStream(pout);
this.examConfigIO.exportPlain( this.examConfigIO.exportPlain(
exportFormat, exportFormat,
pout, pout,
institutionId, institutionId,
configurationNodeId); configurationNodeId);
IOUtils.copyLarge(pin, out); IOUtils.copyLarge(pin, out);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Error while stream plain text SEB Configuration export data: ", e); log.error("Error while stream plain text SEB Configuration export data: ", e);
} finally { } finally {
try { try {
if (pin != null) { if (pin != null) {
pin.close(); pin.close();
} }
} catch (final IOException e1) { } catch (final IOException e1) {
log.error("Failed to close PipedInputStream: ", e1); log.error("Failed to close PipedInputStream: ", e1);
} }
try { try {
if (pout != null) { if (pout != null) {
pout.flush(); pout.flush();
pout.close(); pout.close();
} }
} catch (final IOException e1) { } catch (final IOException e1) {
log.error("Failed to close PipedOutputStream: ", e1); log.error("Failed to close PipedOutputStream: ", e1);
} }
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("Finished to stream plain text SEB Configuration export data"); log.debug("Finished to stream plain text SEB Configuration export data");
} }
} }
} }
} }

View file

@ -1,100 +1,133 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.converter; package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.converter;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import org.apache.commons.lang3.StringUtils; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import org.springframework.context.annotation.Lazy; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.ExamConfigXMLParser;
import org.springframework.stereotype.Component; import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverter; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
@Lazy import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverter;
@Component
@WebServiceProfile @Lazy
public class StringConverter implements AttributeValueConverter { @Component
@WebServiceProfile
public static final Set<AttributeType> SUPPORTED_TYPES = Collections.unmodifiableSet( public class StringConverter implements AttributeValueConverter {
new HashSet<>(Arrays.asList(
AttributeType.TEXT_FIELD, public static final Set<AttributeType> SUPPORTED_TYPES = Collections.unmodifiableSet(
AttributeType.TEXT_AREA, new HashSet<>(Arrays.asList(
AttributeType.PASSWORD_FIELD, AttributeType.TEXT_FIELD,
AttributeType.DECIMAL, AttributeType.TEXT_AREA,
AttributeType.COMBO_SELECTION))); AttributeType.PASSWORD_FIELD,
AttributeType.DECIMAL,
private static final String XML_TEMPLATE = "<key>%s</key><string>%s</string>"; AttributeType.COMBO_SELECTION)));
private static final String XML_TEMPLATE_EMPTY = "<key>%s</key><string />";
private static final String JSON_TEMPLATE = "\"%s\":\"%s\"";
private static final String JSON_TEMPLATE_EMPTY = "\"%s\":\"\""; private static final String XML_TEMPLATE = "<key>%s</key><string>%s</string>";
private static final String XML_TEMPLATE_EMPTY = "<key>%s</key><string />";
@Override
public Set<AttributeType> types() { private static final String JSON_TEMPLATE = "\"%s\":\"%s\"";
return SUPPORTED_TYPES; private static final String JSON_TEMPLATE_EMPTY = "\"%s\":\"\"";
}
private final ClientCredentialService clientCredentialService;
@Override
public void convertToXML( public StringConverter(final ClientCredentialService clientCredentialService) {
final OutputStream out, this.clientCredentialService = clientCredentialService;
final ConfigurationAttribute attribute, }
final Function<ConfigurationAttribute, ConfigurationValue> valueSupplier) throws IOException {
@Override
convert( public Set<AttributeType> types() {
out, return SUPPORTED_TYPES;
attribute, }
valueSupplier.apply(attribute),
XML_TEMPLATE, XML_TEMPLATE_EMPTY); @Override
} public void convertToXML(
final OutputStream out,
@Override final ConfigurationAttribute attribute,
public void convertToJSON( final Function<ConfigurationAttribute, ConfigurationValue> valueSupplier) throws IOException {
final OutputStream out,
final ConfigurationAttribute attribute, convert(
final Function<ConfigurationAttribute, ConfigurationValue> valueSupplier) throws IOException { out,
attribute,
convert( valueSupplier.apply(attribute),
out, XML_TEMPLATE, XML_TEMPLATE_EMPTY);
attribute, }
valueSupplier.apply(attribute),
JSON_TEMPLATE, JSON_TEMPLATE_EMPTY); @Override
} public void convertToJSON(
final OutputStream out,
private void convert( final ConfigurationAttribute attribute,
final OutputStream out, final Function<ConfigurationAttribute, ConfigurationValue> valueSupplier) throws IOException {
final ConfigurationAttribute attribute,
final ConfigurationValue value, convert(
final String template, out,
final String emptyTemplate) throws IOException { attribute,
valueSupplier.apply(attribute),
final String val = (value != null && value.value != null) ? value.value : attribute.getDefaultValue(); JSON_TEMPLATE, JSON_TEMPLATE_EMPTY);
if (StringUtils.isNotBlank(val)) { }
out.write(Utils.toByteArray(String.format(
template, private void convert(
AttributeValueConverter.extractName(attribute), final OutputStream out,
val))); final ConfigurationAttribute attribute,
} else { final ConfigurationValue value,
out.write(Utils.toByteArray(String.format( final String template,
emptyTemplate, final String emptyTemplate) throws IOException {
AttributeValueConverter.extractName(attribute))));
} 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,
realName,
convertPassword(realName, val))));
} else {
out.write(Utils.toByteArray(String.format(
emptyTemplate,
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);
}
}
}

View file

@ -1,79 +1,87 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.init; package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.init;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.function.Function; import java.util.function.Function;
import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory; import javax.xml.parsers.SAXParserFactory;
import org.apache.tomcat.util.http.fileupload.IOUtils; import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.slf4j.Logger; import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.LoggerFactory; import org.slf4j.Logger;
import org.springframework.context.annotation.Lazy; import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.ExamConfigXMLParser; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.ExamConfigXMLParser;
@Lazy
@Component @Lazy
@WebServiceProfile @Component
public class XMLAttributeLoader { @WebServiceProfile
public class XMLAttributeLoader {
private static final Logger log = LoggerFactory.getLogger(XMLAttributeLoader.class);
private static final Logger log = LoggerFactory.getLogger(XMLAttributeLoader.class);
public Collection<ConfigurationValue> loadFromXML(
final Long institutionId, private final Cryptor cryptor;
final Long configurationId,
final Function<String, ConfigurationAttribute> attributeResolver, public XMLAttributeLoader(Cryptor cryptor) {
final String xmlFileName) { this.cryptor = cryptor;
}
InputStream inputStream;
try { public Collection<ConfigurationValue> loadFromXML(
final ClassPathResource configFileResource = new ClassPathResource(xmlFileName); final Long institutionId,
inputStream = configFileResource.getInputStream(); final Long configurationId,
} catch (final Exception e) { final Function<String, ConfigurationAttribute> attributeResolver,
log.error("Failed to get config resources from: {}", xmlFileName, e); final String xmlFileName) {
return Collections.emptyList();
} InputStream inputStream;
try {
try { final ClassPathResource configFileResource = new ClassPathResource(xmlFileName);
inputStream = configFileResource.getInputStream();
final Collection<ConfigurationValue> values = new ArrayList<>(); } catch (final Exception e) {
log.error("Failed to get config resources from: {}", xmlFileName, e);
final ExamConfigXMLParser examConfigImportHandler = new ExamConfigXMLParser( return Collections.emptyList();
institutionId, }
configurationId,
values::add, try {
attributeResolver);
final Collection<ConfigurationValue> values = new ArrayList<>();
final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
final SAXParser parser = saxParserFactory.newSAXParser(); final ExamConfigXMLParser examConfigImportHandler = new ExamConfigXMLParser(
parser.parse(inputStream, examConfigImportHandler); cryptor,
institutionId,
return Utils.immutableCollectionOf(values); configurationId,
values::add,
} catch (final Exception e) { attributeResolver);
log.error("Unexpected error while trying to get initial permitted processes", e);
return Collections.emptyList(); final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
} finally { final SAXParser parser = saxParserFactory.newSAXParser();
IOUtils.closeQuietly(inputStream); parser.parse(inputStream, examConfigImportHandler);
}
} return Utils.immutableCollectionOf(values);
} } catch (final Exception e) {
log.error("Unexpected error while trying to get initial permitted processes", e);
return Collections.emptyList();
} finally {
IOUtils.closeQuietly(inputStream);
}
}
}

View file

@ -1,166 +1,215 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.weblayer.api; package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.io.IOException; import java.io.IOException;
import java.io.PipedInputStream; import java.io.PipedInputStream;
import java.io.PipedOutputStream; import java.io.PipedOutputStream;
import java.util.ArrayList;
import javax.servlet.ServletOutputStream; import java.util.Collection;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletOutputStream;
import org.apache.commons.io.IOUtils; import javax.servlet.http.HttpServletResponse;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.apache.commons.io.IOUtils;
import org.mybatis.dynamic.sql.SqlTable; import org.apache.commons.lang3.BooleanUtils;
import org.springframework.http.HttpStatus; import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType; import org.joda.time.DateTime;
import org.springframework.scheduling.annotation.EnableAsync; import org.joda.time.DateTimeZone;
import org.springframework.validation.FieldError; import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.bind.annotation.RestController; import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PathVariable;
import ch.ethz.seb.sebserver.gbl.Constants; import org.springframework.web.bind.annotation.RequestMapping;
import ch.ethz.seb.sebserver.gbl.api.API; import org.springframework.web.bind.annotation.RequestMethod;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange; import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordDynamicSqlSupport; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SebClientConfigDAO; import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordDynamicSqlSupport;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SebClientConfigDAO;
@WebServiceProfile import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
@RestController import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
@EnableAsync import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_CONFIG_ENDPOINT)
public class SebClientConfigController extends ActivatableEntityController<SebClientConfig, SebClientConfig> { @WebServiceProfile
@RestController
private final ClientConfigService sebClientConfigService; @EnableAsync
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.SEB_CLIENT_CONFIG_ENDPOINT)
public SebClientConfigController( public class SebClientConfigController extends ActivatableEntityController<SebClientConfig, SebClientConfig> {
final SebClientConfigDAO sebClientConfigDAO,
final AuthorizationService authorization, private final ClientConfigService sebClientConfigService;
final UserActivityLogDAO userActivityLogDAO,
final BulkActionService bulkActionService, public SebClientConfigController(
final PaginationService paginationService, final SebClientConfigDAO sebClientConfigDAO,
final BeanValidationService beanValidationService, final AuthorizationService authorization,
final ClientConfigService sebClientConfigService) { final UserActivityLogDAO userActivityLogDAO,
final BulkActionService bulkActionService,
super(authorization, final PaginationService paginationService,
bulkActionService, final BeanValidationService beanValidationService,
sebClientConfigDAO, final ClientConfigService sebClientConfigService) {
userActivityLogDAO,
paginationService, super(authorization,
beanValidationService); bulkActionService,
sebClientConfigDAO,
this.sebClientConfigService = sebClientConfigService; userActivityLogDAO,
} paginationService,
beanValidationService);
@RequestMapping(
path = API.SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT, this.sebClientConfigService = sebClientConfigService;
method = RequestMethod.GET, }
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void downloadSEBConfig( @RequestMapping(
@PathVariable final String modelId, path = API.SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT,
final HttpServletResponse response) throws IOException { method = RequestMethod.GET,
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
this.entityDAO.byModelId(modelId) public void downloadSEBConfig(
.flatMap(this.authorization::checkWrite) @PathVariable final String modelId,
.map(this.userActivityLogDAO::logExport); final HttpServletResponse response) throws IOException {
final ServletOutputStream outputStream = response.getOutputStream(); this.entityDAO.byModelId(modelId)
PipedOutputStream pout = null; .flatMap(this.authorization::checkWrite)
PipedInputStream pin = null; .map(this.userActivityLogDAO::logExport);
try {
pout = new PipedOutputStream(); final ServletOutputStream outputStream = response.getOutputStream();
pin = new PipedInputStream(pout); PipedOutputStream pout = null;
PipedInputStream pin = null;
this.sebClientConfigService.exportSebClientConfiguration( try {
pout, pout = new PipedOutputStream();
modelId); pin = new PipedInputStream(pout);
IOUtils.copyLarge(pin, outputStream); this.sebClientConfigService.exportSebClientConfiguration(
pout,
response.setStatus(HttpStatus.OK.value()); modelId);
outputStream.flush(); IOUtils.copyLarge(pin, outputStream);
} finally { response.setStatus(HttpStatus.OK.value());
outputStream.flush();
outputStream.close(); outputStream.flush();
}
} finally {
// final StreamingResponseBody stream = out -> { outputStream.flush();
// this.sebClientConfigService.exportSebClientConfiguration( outputStream.close();
// out, }
// modelId); }
// };
// @Override
// return new ResponseEntity<>(stream, HttpStatus.OK); protected SebClientConfig createNew(final POSTMapper postParams) {
}
final Long institutionId = postParams.getLong(
@Override Domain.SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID);
protected SebClientConfig createNew(final POSTMapper postParams) {
if (institutionId == null) {
final Long institutionId = postParams.getLong( throw new APIConstraintViolationException("Institution identifier is missing");
Domain.SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID); }
if (institutionId == null) { postParams.putIfAbsent(
throw new APIConstraintViolationException("Institution identifier is missing"); Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE,
} DateTime.now(DateTimeZone.UTC).toString(Constants.DEFAULT_DATE_TIME_FORMAT));
postParams.putIfAbsent( return new SebClientConfig(institutionId, postParams);
Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE, }
DateTime.now(DateTimeZone.UTC).toString(Constants.DEFAULT_DATE_TIME_FORMAT));
@Override
return new SebClientConfig(institutionId, postParams); protected SqlTable getSQLTableOfEntity() {
} return SebClientConfigRecordDynamicSqlSupport.sebClientConfigRecord;
}
@Override
protected SqlTable getSQLTableOfEntity() { @Override
return SebClientConfigRecordDynamicSqlSupport.sebClientConfigRecord; protected Result<SebClientConfig> validForCreate(final SebClientConfig entity) {
} return super.validForCreate(entity)
.map(this::checkPasswordMatch);
@Override }
protected Result<SebClientConfig> validForCreate(final SebClientConfig entity) {
return super.validForCreate(entity) @Override
.map(this::checkPasswordMatch); protected Result<SebClientConfig> validForSave(final SebClientConfig entity) {
} return super.validForSave(entity)
.map(this::checkPasswordMatch);
@Override }
protected Result<SebClientConfig> validForSave(final SebClientConfig entity) {
return super.validForSave(entity) private SebClientConfig checkPasswordMatch(final SebClientConfig entity) {
.map(this::checkPasswordMatch); Collection<APIMessage> errors = new ArrayList<>();
} if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.encryptSecretConfirm)) {
errors.add(APIMessage.fieldValidationError(
private SebClientConfig checkPasswordMatch(final SebClientConfig entity) { new FieldError(
if (entity.hasEncryptionSecret() && !entity.encryptSecret.equals(entity.confirmEncryptSecret)) { Domain.SEB_CLIENT_CONFIGURATION.TYPE_NAME,
throw new APIMessageException(APIMessage.fieldValidationError( PasswordChange.ATTR_NAME_PASSWORD,
new FieldError( "clientConfig:confirm_encrypt_secret:password.mismatch")));
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(
return entity; 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;
}
}

View file

@ -23,6 +23,8 @@ sebserver.overall.action.goAwayFromEditPageConfirm=Are you sure you want to leav
sebserver.overall.action.category.varia= sebserver.overall.action.category.varia=
sebserver.overall.action.category.filter= sebserver.overall.action.category.filter=
sebserver.overall.action.showPassword.tooltip=Show / hide password in plain text.
sebserver.overall.status.active=Active sebserver.overall.status.active=Active
sebserver.overall.status.inactive=Inactive sebserver.overall.status.inactive=Inactive
sebserver.overall.status.all=All 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 sebserver.exam.indicator.thresholds.list.remove=Delete this threshold
################################ ################################
# SEB Client Configuration # SEB client configuration
################################ ################################
sebserver.sebconfig.activity.name=SEB Configuration sebserver.sebconfig.activity.name=SEB Configuration
@ -576,7 +578,7 @@ sebserver.clientconfig.action.list=Client Configuration
sebserver.clientconfig.action.export=Export 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.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.actions=
sebserver.clientconfig.list.column.institution=Institution 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} 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=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.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.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.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=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=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=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.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=Fallback Timeout
sebserver.clientconfig.form.sebServerFallbackTimeout.tooltip=Defines the fallback timeout for the SEB Client in milli-seconds. 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=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.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=Interval
sebserver.clientconfig.form.sebServerFallbackAttemptInterval.tooltip=The interval (in milli-seconds) between connection attempts a SEB Client shall use. 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=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=Creation Date
sebserver.clientconfig.form.date.tooltip=The date when the SEB client configuration was first created. sebserver.clientconfig.form.date.tooltip=The date when the SEB client configuration was first created.
sebserver.clientconfig.form.encryptSecret=Configuration Password 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=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.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.new=Add Configuration
sebserver.clientconfig.action.list.view=View Configuration sebserver.clientconfig.action.list.view=View Configuration
sebserver.clientconfig.action.list.modify=Edit Configuration sebserver.clientconfig.action.list.modify=Edit Configuration

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

View file

@ -1,150 +1,171 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.integration; package ch.ethz.seb.sebserver.gui.integration;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport; import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestServiceImpl; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestServiceImpl;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.ActivateClientConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.ActivateClientConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.DeactivateClientConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.DeactivateClientConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.GetClientConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.GetClientConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.NewClientConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.NewClientConfig;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.SaveClientConfig; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.SaveClientConfig;
@Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql" }) @Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql" })
public class ClientConfigTest extends GuiIntegrationTest { public class ClientConfigTest extends GuiIntegrationTest {
@Test @Test
public void testNewClientConfigWithQueryParam() { public void testNewClientConfigWithQueryParam() {
final RestServiceImpl restService = createRestServiceForUser("admin", "admin", new NewClientConfig()); final RestServiceImpl restService = createRestServiceForUser("admin", "admin", new NewClientConfig());
final Result<SebClientConfig> call = restService.getBuilder(NewClientConfig.class) final Result<SebClientConfig> call = restService.getBuilder(NewClientConfig.class)
.withQueryParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config") .withQueryParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config")
.call(); .withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
.call();
assertNotNull(call);
assertFalse(call.hasError()); assertNotNull(call);
final SebClientConfig createdConfig = call.get(); assertFalse(call.hasError());
assertEquals(Long.valueOf(1), createdConfig.id); final SebClientConfig createdConfig = call.get();
assertEquals("new client config", createdConfig.name); assertEquals(Long.valueOf(1), createdConfig.id);
assertFalse(createdConfig.active); assertEquals("new client config", createdConfig.name);
} assertFalse(createdConfig.active);
}
@Test
public void testNewClientConfigWithURLEncodedForm() { @Test
final RestServiceImpl restService = createRestServiceForUser("admin", "admin", new NewClientConfig()); public void testNewClientConfigWithURLEncodedForm() {
final RestServiceImpl restService = createRestServiceForUser("admin", "admin", new NewClientConfig());
final Result<SebClientConfig> call = restService.getBuilder(NewClientConfig.class)
.withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config") final Result<SebClientConfig> call = restService.getBuilder(NewClientConfig.class)
.call(); .withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config")
.withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
assertNotNull(call); .call();
assertFalse(call.hasError());
final SebClientConfig createdConfig = call.get(); assertNotNull(call);
assertEquals(Long.valueOf(1), createdConfig.id); assertFalse(call.hasError());
assertEquals("new client config", createdConfig.name); final SebClientConfig createdConfig = call.get();
assertFalse(createdConfig.active); assertEquals(Long.valueOf(1), createdConfig.id);
} assertEquals("new client config", createdConfig.name);
assertFalse(createdConfig.active);
@Test }
public void testCreate_Get_Activate_Save_Deactivate_ClientConfig() {
final RestServiceImpl restService = createRestServiceForUser("admin", "admin", @Test
new NewClientConfig(), public void testCreate_Get_Activate_Save_Deactivate_ClientConfig() {
new GetClientConfig(), final RestServiceImpl restService = createRestServiceForUser("admin", "admin",
new ActivateClientConfig(), new NewClientConfig(),
new SaveClientConfig(), new GetClientConfig(),
new DeactivateClientConfig()); new ActivateClientConfig(),
new SaveClientConfig(),
// create one new DeactivateClientConfig());
final SebClientConfig config = restService.getBuilder(NewClientConfig.class)
.withQueryParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config") // create one
.call() final SebClientConfig config = restService.getBuilder(NewClientConfig.class)
.getOrThrow(); .withQueryParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config")
.withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
// get .call()
final Result<SebClientConfig> call = restService.getBuilder(GetClientConfig.class) .getOrThrow();
.withURIVariable(API.PARAM_MODEL_ID, config.getModelId())
.call(); // get
final Result<SebClientConfig> call = restService.getBuilder(GetClientConfig.class)
assertNotNull(call); .withURIVariable(API.PARAM_MODEL_ID, config.getModelId())
assertFalse(call.hasError()); .call();
final SebClientConfig createdConfig = call.get();
assertEquals(config.id, createdConfig.id); assertNotNull(call);
assertEquals("new client config", createdConfig.name); assertFalse(call.hasError());
assertFalse(createdConfig.active); final SebClientConfig createdConfig = call.get();
assertEquals(config.id, createdConfig.id);
// activate assertEquals("new client config", createdConfig.name);
final EntityProcessingReport activationReport = restService.getBuilder(ActivateClientConfig.class) assertFalse(createdConfig.active);
.withURIVariable(API.PARAM_MODEL_ID, config.getModelId())
.call() // activate
.getOrThrow(); final EntityProcessingReport activationReport = restService.getBuilder(ActivateClientConfig.class)
.withURIVariable(API.PARAM_MODEL_ID, config.getModelId())
assertTrue(activationReport.errors.isEmpty()); .call()
assertEquals( .getOrThrow();
"EntityKey [modelId=1, entityType=SEB_CLIENT_CONFIGURATION]",
activationReport.getSingleSource().toString()); assertTrue(activationReport.errors.isEmpty());
assertEquals(
// save with password (no confirm) expecting validation error "EntityKey [modelId=1, entityType=SEB_CLIENT_CONFIGURATION]",
final Result<?> valError = restService.getBuilder(SaveClientConfig.class) activationReport.getSingleSource().toString());
.withBody(new SebClientConfig(
config.id, // save with password (no confirm) expecting validation error
config.institutionId, final Result<?> valError = restService.getBuilder(SaveClientConfig.class)
"new client config", .withBody(new SebClientConfig(
null, config.id,
null, config.institutionId,
"password", "new client config",
null, SebClientConfig.ConfigPurpose.START_EXAM,
null)) null,
.call(); null,
null,
assertTrue(valError.hasError()); null,
final Throwable error = valError.getError(); null,
assertTrue(error.getMessage().contains("confirm_encrypt_secret")); null,
assertTrue(error.getMessage().contains("password.mismatch")); null,
null,
// save with new password null,
final SebClientConfig newConfig = restService.getBuilder(SaveClientConfig.class) null,
.withBody(new SebClientConfig( "password",
config.id, null,
config.institutionId, null))
"new client config", .call();
null,
null, assertTrue(valError.hasError());
"password", final Throwable error = valError.getError();
"password", assertTrue(error.getMessage().contains("confirm_encrypt_secret"));
null)) assertTrue(error.getMessage().contains("password.mismatch"));
.call()
.getOrThrow(); // save with new password
final SebClientConfig newConfig = restService.getBuilder(SaveClientConfig.class)
assertEquals(config.id, newConfig.id); .withBody(new SebClientConfig(
assertEquals("new client config", newConfig.name); config.id,
assertTrue(newConfig.active); config.institutionId,
assertNull(newConfig.getEncryptSecret()); "new client config",
SebClientConfig.ConfigPurpose.START_EXAM,
// deactivate null,
final EntityProcessingReport deactivationReport = restService.getBuilder(DeactivateClientConfig.class) null,
.withURIVariable(API.PARAM_MODEL_ID, config.getModelId()) null,
.call() null,
.getOrThrow(); null,
null,
assertTrue(deactivationReport.errors.isEmpty()); null,
assertEquals( null,
"EntityKey [modelId=1, entityType=SEB_CLIENT_CONFIGURATION]", null,
deactivationReport.getSingleSource().toString()); null,
} "password",
"password",
} null))
.call()
.getOrThrow();
assertEquals(config.id, newConfig.id);
assertEquals("new client config", newConfig.name);
assertTrue(newConfig.active);
assertNotNull(newConfig.getEncryptSecret());
// deactivate
final EntityProcessingReport deactivationReport = restService.getBuilder(DeactivateClientConfig.class)
.withURIVariable(API.PARAM_MODEL_ID, config.getModelId())
.call()
.getOrThrow();
assertTrue(deactivationReport.errors.isEmpty());
assertEquals(
"EntityKey [modelId=1, entityType=SEB_CLIENT_CONFIGURATION]",
deactivationReport.getSingleSource().toString());
}
}

View file

@ -12,6 +12,7 @@ import static org.junit.Assert.*;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
@ -853,7 +854,12 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
final Result<SebClientConfig> newConfigResponse = restService final Result<SebClientConfig> newConfigResponse = restService
.getBuilder(NewClientConfig.class) .getBuilder(NewClientConfig.class)
.withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "No Password Protection") .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_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(); .call();
assertNotNull(newConfigResponse); assertNotNull(newConfigResponse);
@ -886,9 +892,14 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
final Result<SebClientConfig> configWithPasswordResponse = restService final Result<SebClientConfig> configWithPasswordResponse = restService
.getBuilder(NewClientConfig.class) .getBuilder(NewClientConfig.class)
.withFormParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "With Password Protection") .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_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(SEB_CLIENT_CONFIGURATION.ATTR_ENCRYPT_SECRET, "123")
.withFormParam(SebClientConfig.ATTR_CONFIRM_ENCRYPT_SECRET, "123") .withFormParam(SebClientConfig.ATTR_ENCRYPT_SECRET_CONFIRM, "123")
.call(); .call();
assertNotNull(configWithPasswordResponse); assertNotNull(configWithPasswordResponse);
@ -1091,7 +1102,10 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
restService); restService);
// update a value -- grab first // 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( ConfigurationValue newValue = new ConfigurationValue(
null, value.institutionId, value.configurationId, null, value.institutionId, value.configurationId,
value.attributeId, value.listIndex, "2"); value.attributeId, value.listIndex, "2");
@ -1188,8 +1202,9 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
assertNotNull(valuesResponse); assertNotNull(valuesResponse);
assertFalse(valuesResponse.hasError()); assertFalse(valuesResponse.hasError());
values = valuesResponse.get(); values = valuesResponse.get();
final ConfigurationValue _value = value;
final ConfigurationValue currentValue = 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); assertNotNull(currentValue);
assertEquals("2", currentValue.value); assertEquals("2", currentValue.value);
} }
@ -1336,12 +1351,10 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
new GetFollowupConfiguration()); new GetFollowupConfiguration());
// get all configuration attributes // get all configuration attributes
final Collection<ConfigurationAttribute> attributes = restService final Collection<ConfigurationAttribute> attributes = new ArrayList<>(restService
.getBuilder(GetConfigAttributes.class) .getBuilder(GetConfigAttributes.class)
.call() .call()
.getOrThrow() .getOrThrow());
.stream()
.collect(Collectors.toList());
// get configuration page // get configuration page
final Result<Page<ConfigurationNode>> pageResponse = restService final Result<Page<ConfigurationNode>> pageResponse = restService

View file

@ -1,132 +1,134 @@
/* /*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.gui.integration; package ch.ethz.seb.sebserver.gui.integration;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.tomcat.util.buf.StringUtils; import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.apache.tomcat.util.buf.StringUtils;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestServiceImpl; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationTableValues;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigAttributes; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestServiceImpl;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigurationTableValues; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigAttributes;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.init.XMLAttributeLoader; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigurationTableValues;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.init.XMLAttributeLoader;
public abstract class UsecaseTestUtils { import org.mockito.Mockito;
static ConfigurationTableValues testProhibitedProcessesInit( public abstract class UsecaseTestUtils {
final String configId,
final RestServiceImpl restService) { static ConfigurationTableValues testProhibitedProcessesInit(
final String configId,
final ConfigurationTableValues tableValues = getTableValues("93", configId, restService); final RestServiceImpl restService) {
assertNotNull(tableValues); final ConfigurationTableValues tableValues = getTableValues("93", configId, restService);
assertFalse(tableValues.values.isEmpty());
final String names = StringUtils.join( assertNotNull(tableValues);
tableValues.values assertFalse(tableValues.values.isEmpty());
.stream() final String names = StringUtils.join(
.filter(attr -> attr.attributeId == 98) tableValues.values
.map(attr -> attr.value) .stream()
.sorted() .filter(attr -> attr.attributeId == 98)
.collect(Collectors.toList()), .map(attr -> attr.value)
Constants.LIST_SEPARATOR_CHAR); .sorted()
.collect(Collectors.toList()),
// get all configuration attributes Constants.LIST_SEPARATOR_CHAR);
final Map<String, ConfigurationAttribute> attributes = restService
.getBuilder(GetConfigAttributes.class) // get all configuration attributes
.call() final Map<String, ConfigurationAttribute> attributes = restService
.getOrThrow() .getBuilder(GetConfigAttributes.class)
.stream() .call()
.collect(Collectors.toMap(attr -> attr.name, Function.identity())); .getOrThrow()
.stream()
final XMLAttributeLoader xmlAttributeLoader = new XMLAttributeLoader(); .collect(Collectors.toMap(attr -> attr.name, Function.identity()));
final String configuraedNames = StringUtils.join(xmlAttributeLoader.loadFromXML(
1L, final XMLAttributeLoader xmlAttributeLoader = new XMLAttributeLoader(Mockito.mock(Cryptor.class));
Long.parseLong(configId), final String configuraedNames = StringUtils.join(xmlAttributeLoader.loadFromXML(
attrName -> attributes.get(attrName), 1L,
"config/initialProhibitedProcesses.xml") Long.parseLong(configId),
.stream() attrName -> attributes.get(attrName),
.filter(attr -> attr.attributeId == 98) "config/initialProhibitedProcesses.xml")
.map(attr -> attr.value) .stream()
.sorted() .filter(attr -> attr.attributeId == 98)
.collect(Collectors.toList()), .map(attr -> attr.value)
Constants.LIST_SEPARATOR_CHAR); .sorted()
.collect(Collectors.toList()),
assertEquals(configuraedNames, names); Constants.LIST_SEPARATOR_CHAR);
return tableValues; assertEquals(configuraedNames, names);
}
return tableValues;
static ConfigurationTableValues getTableValues( }
final String attributeId,
final String configId, static ConfigurationTableValues getTableValues(
final RestServiceImpl restService) { final String attributeId,
final ConfigurationTableValues tableValues = restService.getBuilder(GetConfigurationTableValues.class) final String configId,
.withQueryParam( final RestServiceImpl restService) {
Domain.CONFIGURATION_VALUE.ATTR_CONFIGURATION_ATTRIBUTE_ID, final ConfigurationTableValues tableValues = restService.getBuilder(GetConfigurationTableValues.class)
attributeId) .withQueryParam(
.withQueryParam( Domain.CONFIGURATION_VALUE.ATTR_CONFIGURATION_ATTRIBUTE_ID,
Domain.CONFIGURATION_VALUE.ATTR_CONFIGURATION_ID, attributeId)
configId) .withQueryParam(
.call() Domain.CONFIGURATION_VALUE.ATTR_CONFIGURATION_ID,
.getOrThrow(); configId)
return tableValues; .call()
} .getOrThrow();
return tableValues;
static ConfigurationTableValues testPermittedProcessesInit( }
final String configId,
final RestServiceImpl restService) { static ConfigurationTableValues testPermittedProcessesInit(
final String configId,
final ConfigurationTableValues tableValues = getTableValues("73", configId, restService); final RestServiceImpl restService) {
assertNotNull(tableValues); final ConfigurationTableValues tableValues = getTableValues("73", configId, restService);
assertFalse(tableValues.values.isEmpty());
final String names = StringUtils.join( assertNotNull(tableValues);
tableValues.values assertFalse(tableValues.values.isEmpty());
.stream() final String names = StringUtils.join(
.filter(attr -> attr.attributeId == 76) tableValues.values
.map(attr -> attr.value) .stream()
.sorted() .filter(attr -> attr.attributeId == 76)
.collect(Collectors.toList()), .map(attr -> attr.value)
Constants.LIST_SEPARATOR_CHAR); .sorted()
.collect(Collectors.toList()),
// get all configuration attributes Constants.LIST_SEPARATOR_CHAR);
final Map<String, ConfigurationAttribute> attributes = restService
.getBuilder(GetConfigAttributes.class) // get all configuration attributes
.call() final Map<String, ConfigurationAttribute> attributes = restService
.getOrThrow() .getBuilder(GetConfigAttributes.class)
.stream() .call()
.collect(Collectors.toMap(attr -> attr.name, Function.identity())); .getOrThrow()
.stream()
final XMLAttributeLoader xmlAttributeLoader = new XMLAttributeLoader(); .collect(Collectors.toMap(attr -> attr.name, Function.identity()));
final String configuraedNames = StringUtils.join(xmlAttributeLoader.loadFromXML(
1L, final XMLAttributeLoader xmlAttributeLoader = new XMLAttributeLoader(Mockito.mock(Cryptor.class));
Long.parseLong(configId), final String configuraedNames = StringUtils.join(xmlAttributeLoader.loadFromXML(
attrName -> attributes.get(attrName), 1L,
"config/initialPermittedProcesses.xml") Long.parseLong(configId),
.stream() attrName -> attributes.get(attrName),
.filter(attr -> attr.attributeId == 76) "config/initialPermittedProcesses.xml")
.map(attr -> attr.value) .stream()
.sorted() .filter(attr -> attr.attributeId == 76)
.collect(Collectors.toList()), .map(attr -> attr.value)
Constants.LIST_SEPARATOR_CHAR); .sorted()
.collect(Collectors.toList()),
assertEquals(configuraedNames, names); Constants.LIST_SEPARATOR_CHAR);
return tableValues; assertEquals(configuraedNames, names);
}
return tableValues;
} }
}

View file

@ -1,55 +1,58 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.client; package ch.ethz.seb.sebserver.webservice.servicelayer.client;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import org.junit.Test; import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import org.springframework.core.env.Environment; import org.junit.Test;
import org.springframework.core.env.Environment;
public class ClientCredentialServiceTest {
public class ClientCredentialServiceTest {
// @Test
// public void testEncryptSimpleSecret() { // @Test
// final Environment envMock = mock(Environment.class); // public void testEncryptSimpleSecret() {
// when(envMock.getProperty(ClientCredentialServiceImpl.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY)) // final Environment envMock = mock(Environment.class);
// .thenReturn("somePW"); // when(envMock.getProperty(ClientCredentialServiceImpl.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY))
// // .thenReturn("somePW");
// final ClientCredentialService service = new ClientCredentialServiceImpl(envMock); //
// final CharSequence encrypt = service.encrypt("test"); // final ClientCredentialService service = new ClientCredentialServiceImpl(envMock);
// assertEquals("", encrypt.toString()); // final CharSequence encrypt = service.encrypt("test");
// } // assertEquals("", encrypt.toString());
// }
@Test
public void testEncryptDecryptClientCredentials() { @Test
final Environment envMock = mock(Environment.class); public void testEncryptDecryptClientCredentials() {
when(envMock.getRequiredProperty(ClientCredentialServiceImpl.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY)) final Environment envMock = mock(Environment.class);
.thenReturn("secret1"); when(envMock.getRequiredProperty(Cryptor.SEBSERVER_WEBSERVICE_INTERNAL_SECRET_KEY))
.thenReturn("secret1");
final String clientName = "simpleClientName";
Cryptor cryptor = new Cryptor(envMock);
final ClientCredentialServiceImpl service = new ClientCredentialServiceImpl(envMock);
String encrypted = final String clientName = "simpleClientName";
service.encrypt(clientName, "secret1").toString();
String decrypted = service.decrypt(encrypted, "secret1").toString(); final ClientCredentialServiceImpl service = new ClientCredentialServiceImpl(envMock, cryptor);
String encrypted =
assertEquals(clientName, decrypted); cryptor.encrypt(clientName, "secret1").toString();
String decrypted = cryptor.decrypt(encrypted, "secret1").toString();
final String clientSecret = "fbjreij39ru29305ruࣣàèLöäöäü65%(/%(ç87";
assertEquals(clientName, decrypted);
encrypted =
service.encrypt(clientSecret, "secret1").toString(); final String clientSecret = "fbjreij39ru29305ruࣣàèLöäöäü65%(/%(ç87";
decrypted = service.decrypt(encrypted, "secret1").toString();
encrypted =
assertEquals(clientSecret, decrypted); cryptor.encrypt(clientSecret, "secret1").toString();
} decrypted = cryptor.decrypt(encrypted, "secret1").toString();
} assertEquals(clientSecret, decrypted);
}
}

View file

@ -1,339 +1,347 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import org.junit.Test; 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.AttributeType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
public class ExamConfigImportHandlerTest { import org.mockito.Mockito;
private static final Function<String, ConfigurationAttribute> attributeResolver = public class ExamConfigImportHandlerTest {
name -> new ConfigurationAttribute(
getId(name), private static final Function<String, ConfigurationAttribute> attributeResolver =
null, name, (name.contains("array")) ? AttributeType.MULTI_SELECTION : null, null, null, null, name -> new ConfigurationAttribute(
null); getId(name),
null, name, (name.contains("array")) ? AttributeType.MULTI_SELECTION : null, null, null, null,
private static final Long getId(final String name) { null);
try {
return Long.parseLong(String.valueOf(name.charAt(name.length() - 1))); private static final Long getId(final String name) {
} catch (final Exception e) { try {
return -1L; return Long.parseLong(String.valueOf(name.charAt(name.length() - 1)));
} } catch (final Exception e) {
} return -1L;
}
@Test }
public void simpleStringValueTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector(); @Test
final ExamConfigXMLParser candidate = new ExamConfigXMLParser( public void simpleStringValueTest() throws Exception {
1L, final ValueCollector valueCollector = new ValueCollector();
1L, final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
valueCollector, Mockito.mock(Cryptor.class),
attributeResolver); 1L,
1L,
final String attribute = "param1"; valueCollector,
final String value = "value1"; attributeResolver);
candidate.startElement(null, null, "plist", null); final String attribute = "param1";
candidate.startElement(null, null, "dict", null); final String value = "value1";
candidate.startElement(null, null, "key", null); candidate.startElement(null, null, "plist", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length()); candidate.startElement(null, null, "dict", null);
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "string", null); candidate.startElement(null, null, "key", null);
candidate.characters(value.toCharArray(), 0, value.length()); candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "string"); candidate.endElement(null, null, "key");
candidate.startElement(null, null, "string", null);
candidate.endElement(null, null, "dict"); candidate.characters(value.toCharArray(), 0, value.length());
candidate.endElement(null, null, "plist"); candidate.endElement(null, null, "string");
assertFalse(valueCollector.values.isEmpty()); candidate.endElement(null, null, "dict");
final ConfigurationValue configurationValue = valueCollector.values.get(0); candidate.endElement(null, null, "plist");
assertNotNull(configurationValue);
assertTrue(1L == configurationValue.attributeId); assertFalse(valueCollector.values.isEmpty());
assertEquals("value1", configurationValue.value); final ConfigurationValue configurationValue = valueCollector.values.get(0);
} assertNotNull(configurationValue);
assertTrue(1L == configurationValue.attributeId);
@Test assertEquals("value1", configurationValue.value);
public void simpleIntegerValueTest() throws Exception { }
final ValueCollector valueCollector = new ValueCollector();
final ExamConfigXMLParser candidate = new ExamConfigXMLParser( @Test
1L, public void simpleIntegerValueTest() throws Exception {
1L, final ValueCollector valueCollector = new ValueCollector();
valueCollector, final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
attributeResolver); Mockito.mock(Cryptor.class),
1L,
final String attribute = "param2"; 1L,
final String value = "22"; valueCollector,
attributeResolver);
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null); final String attribute = "param2";
final String value = "22";
candidate.startElement(null, null, "key", null);
candidate.characters(attribute.toCharArray(), 0, attribute.length()); candidate.startElement(null, null, "plist", null);
candidate.endElement(null, null, "key"); candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "integer", null);
candidate.characters(value.toCharArray(), 0, value.length()); candidate.startElement(null, null, "key", null);
candidate.endElement(null, null, "integer"); candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key");
candidate.endElement(null, null, "dict"); candidate.startElement(null, null, "integer", null);
candidate.endElement(null, null, "plist"); candidate.characters(value.toCharArray(), 0, value.length());
candidate.endElement(null, null, "integer");
assertFalse(valueCollector.values.isEmpty());
final ConfigurationValue configurationValue = valueCollector.values.get(0); candidate.endElement(null, null, "dict");
assertNotNull(configurationValue); candidate.endElement(null, null, "plist");
assertTrue(2L == configurationValue.attributeId);
assertEquals("22", configurationValue.value); assertFalse(valueCollector.values.isEmpty());
} final ConfigurationValue configurationValue = valueCollector.values.get(0);
assertNotNull(configurationValue);
@Test assertTrue(2L == configurationValue.attributeId);
public void simpleBooleanValueTest() throws Exception { assertEquals("22", configurationValue.value);
final ValueCollector valueCollector = new ValueCollector(); }
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
1L, @Test
1L, public void simpleBooleanValueTest() throws Exception {
valueCollector, final ValueCollector valueCollector = new ValueCollector();
attributeResolver); final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
Mockito.mock(Cryptor.class),
final String attribute = "param3"; 1L,
final String value = "true"; 1L,
valueCollector,
candidate.startElement(null, null, "plist", null); attributeResolver);
candidate.startElement(null, null, "dict", null);
final String attribute = "param3";
candidate.startElement(null, null, "key", null); final String value = "true";
candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "key"); candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, value, null); candidate.startElement(null, null, "dict", null);
candidate.endElement(null, null, value);
candidate.startElement(null, null, "key", null);
candidate.endElement(null, null, "dict"); candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "plist"); candidate.endElement(null, null, "key");
candidate.startElement(null, null, value, null);
assertFalse(valueCollector.values.isEmpty()); candidate.endElement(null, null, value);
final ConfigurationValue configurationValue = valueCollector.values.get(0);
assertNotNull(configurationValue); candidate.endElement(null, null, "dict");
assertTrue(3L == configurationValue.attributeId); candidate.endElement(null, null, "plist");
assertEquals("true", configurationValue.value);
} assertFalse(valueCollector.values.isEmpty());
final ConfigurationValue configurationValue = valueCollector.values.get(0);
@Test assertNotNull(configurationValue);
public void arrayOfStringValueTest() throws Exception { assertTrue(3L == configurationValue.attributeId);
final ValueCollector valueCollector = new ValueCollector(); assertEquals("true", configurationValue.value);
final ExamConfigXMLParser candidate = new ExamConfigXMLParser( }
1L,
1L, @Test
valueCollector, public void arrayOfStringValueTest() throws Exception {
attributeResolver); final ValueCollector valueCollector = new ValueCollector();
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
final String attribute = "array1"; Mockito.mock(Cryptor.class),
final String value1 = "val1"; 1L,
final String value2 = "val2"; 1L,
final String value3 = "val3"; valueCollector,
attributeResolver);
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null); final String attribute = "array1";
final String value1 = "val1";
candidate.startElement(null, null, "key", null); final String value2 = "val2";
candidate.characters(attribute.toCharArray(), 0, attribute.length()); final String value3 = "val3";
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "array", null); candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "string", null); candidate.startElement(null, null, "key", null);
candidate.characters(value1.toCharArray(), 0, value1.length()); candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.endElement(null, null, "string"); candidate.endElement(null, null, "key");
candidate.startElement(null, null, "string", null);
candidate.characters(value2.toCharArray(), 0, value2.length()); candidate.startElement(null, null, "array", null);
candidate.endElement(null, null, "string");
candidate.startElement(null, null, "string", null); candidate.startElement(null, null, "string", null);
candidate.characters(value3.toCharArray(), 0, value3.length()); candidate.characters(value1.toCharArray(), 0, value1.length());
candidate.endElement(null, null, "string"); candidate.endElement(null, null, "string");
candidate.startElement(null, null, "string", null);
candidate.endElement(null, null, "array"); candidate.characters(value2.toCharArray(), 0, value2.length());
candidate.endElement(null, null, "string");
candidate.endElement(null, null, "dict"); candidate.startElement(null, null, "string", null);
candidate.endElement(null, null, "plist"); candidate.characters(value3.toCharArray(), 0, value3.length());
candidate.endElement(null, null, "string");
assertFalse(valueCollector.values.isEmpty());
assertTrue(valueCollector.values.size() == 1); candidate.endElement(null, null, "array");
final ConfigurationValue configurationValue1 = valueCollector.values.get(0);
assertEquals("val1,val2,val3", configurationValue1.value); candidate.endElement(null, null, "dict");
assertTrue(configurationValue1.listIndex == 0); candidate.endElement(null, null, "plist");
} assertFalse(valueCollector.values.isEmpty());
assertTrue(valueCollector.values.size() == 1);
@Test final ConfigurationValue configurationValue1 = valueCollector.values.get(0);
public void dictOfValuesTest() throws Exception { assertEquals("val1,val2,val3", configurationValue1.value);
final ValueCollector valueCollector = new ValueCollector(); assertTrue(configurationValue1.listIndex == 0);
final List<String> attrNamesCollector = new ArrayList<>();
final Function<String, ConfigurationAttribute> attrConverter = attrName -> { }
attrNamesCollector.add(attrName);
return attributeResolver.apply(attrName); @Test
}; public void dictOfValuesTest() throws Exception {
final ExamConfigXMLParser candidate = new ExamConfigXMLParser( final ValueCollector valueCollector = new ValueCollector();
1L, final List<String> attrNamesCollector = new ArrayList<>();
1L, final Function<String, ConfigurationAttribute> attrConverter = attrName -> {
valueCollector, attrNamesCollector.add(attrName);
attrConverter); return attributeResolver.apply(attrName);
};
final String attribute = "dict1"; final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
Mockito.mock(Cryptor.class),
final String attr1 = "attr1"; 1L,
final String attr2 = "attr2"; 1L,
final String attr3 = "attr3"; valueCollector,
final String value1 = "val1"; attrConverter);
final String value2 = "2";
final String attribute = "dict1";
candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null); final String attr1 = "attr1";
final String attr2 = "attr2";
candidate.startElement(null, null, "key", null); final String attr3 = "attr3";
candidate.characters(attribute.toCharArray(), 0, attribute.length()); final String value1 = "val1";
candidate.endElement(null, null, "key"); final String value2 = "2";
candidate.startElement(null, null, "dict", null); candidate.startElement(null, null, "plist", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attr1.toCharArray(), 0, attr1.length()); candidate.startElement(null, null, "key", null);
candidate.endElement(null, null, "key"); candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.startElement(null, null, "string", null); candidate.endElement(null, null, "key");
candidate.characters(value1.toCharArray(), 0, value1.length());
candidate.endElement(null, null, "string"); candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null); candidate.startElement(null, null, "key", null);
candidate.characters(attr2.toCharArray(), 0, attr2.length()); candidate.characters(attr1.toCharArray(), 0, attr1.length());
candidate.endElement(null, null, "key"); candidate.endElement(null, null, "key");
candidate.startElement(null, null, "integer", null); candidate.startElement(null, null, "string", null);
candidate.characters(value2.toCharArray(), 0, value2.length()); candidate.characters(value1.toCharArray(), 0, value1.length());
candidate.endElement(null, null, "integer"); candidate.endElement(null, null, "string");
candidate.startElement(null, null, "key", null); candidate.startElement(null, null, "key", null);
candidate.characters(attr3.toCharArray(), 0, attr3.length()); candidate.characters(attr2.toCharArray(), 0, attr2.length());
candidate.endElement(null, null, "key"); candidate.endElement(null, null, "key");
candidate.startElement(null, null, "true", null); candidate.startElement(null, null, "integer", null);
candidate.endElement(null, null, "true"); candidate.characters(value2.toCharArray(), 0, value2.length());
candidate.endElement(null, null, "integer");
candidate.endElement(null, null, "dict");
candidate.startElement(null, null, "key", null);
candidate.endElement(null, null, "dict"); candidate.characters(attr3.toCharArray(), 0, attr3.length());
candidate.endElement(null, null, "plist"); candidate.endElement(null, null, "key");
candidate.startElement(null, null, "true", null);
assertFalse(valueCollector.values.isEmpty()); candidate.endElement(null, null, "true");
assertTrue(valueCollector.values.size() == 3);
assertEquals( candidate.endElement(null, null, "dict");
"[ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=0, value=val1], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=0, value=2], " candidate.endElement(null, null, "dict");
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=0, value=true]]", candidate.endElement(null, null, "plist");
valueCollector.values.toString());
assertFalse(valueCollector.values.isEmpty());
assertEquals( assertTrue(valueCollector.values.size() == 3);
"[attr1, attr2, attr3]", assertEquals(
attrNamesCollector.toString()); "[ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=0, value=val1], "
} + "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=0, value=2], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=0, value=true]]",
@Test valueCollector.values.toString());
public void arrayOfDictOfValuesTest() throws Exception {
final ValueCollector valueCollector = new ValueCollector(); assertEquals(
final List<String> attrNamesCollector = new ArrayList<>(); "[attr1, attr2, attr3]",
final Function<String, ConfigurationAttribute> attrConverter = attrName -> { attrNamesCollector.toString());
attrNamesCollector.add(attrName); }
return attributeResolver.apply(attrName);
}; @Test
final ExamConfigXMLParser candidate = new ExamConfigXMLParser( public void arrayOfDictOfValuesTest() throws Exception {
1L, final ValueCollector valueCollector = new ValueCollector();
1L, final List<String> attrNamesCollector = new ArrayList<>();
valueCollector, final Function<String, ConfigurationAttribute> attrConverter = attrName -> {
attrConverter); attrNamesCollector.add(attrName);
return attributeResolver.apply(attrName);
final String attribute = "attribute"; };
final ExamConfigXMLParser candidate = new ExamConfigXMLParser(
final String attr1 = "attr1"; Mockito.mock(Cryptor.class),
final String attr2 = "attr2"; 1L,
final String attr3 = "attr3"; 1L,
final String value1 = "val1"; valueCollector,
final String value2 = "2"; attrConverter);
candidate.startElement(null, null, "plist", null); final String attribute = "attribute";
candidate.startElement(null, null, "dict", null);
final String attr1 = "attr1";
candidate.startElement(null, null, "key", null); final String attr2 = "attr2";
candidate.characters(attribute.toCharArray(), 0, attribute.length()); final String attr3 = "attr3";
candidate.endElement(null, null, "key"); final String value1 = "val1";
final String value2 = "2";
candidate.startElement(null, null, "array", null);
candidate.startElement(null, null, "plist", null);
for (int i = 0; i < 3; i++) { candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.startElement(null, null, "key", null); candidate.characters(attribute.toCharArray(), 0, attribute.length());
candidate.characters(attr1.toCharArray(), 0, attr1.length()); candidate.endElement(null, null, "key");
candidate.endElement(null, null, "key");
candidate.startElement(null, null, "string", null); candidate.startElement(null, null, "array", null);
candidate.characters(value1.toCharArray(), 0, value1.length());
candidate.endElement(null, null, "string"); for (int i = 0; i < 3; i++) {
candidate.startElement(null, null, "dict", null);
candidate.startElement(null, null, "key", null);
candidate.characters(attr2.toCharArray(), 0, attr2.length()); candidate.startElement(null, null, "key", null);
candidate.endElement(null, null, "key"); candidate.characters(attr1.toCharArray(), 0, attr1.length());
candidate.startElement(null, null, "integer", null); candidate.endElement(null, null, "key");
candidate.characters(value2.toCharArray(), 0, value2.length()); candidate.startElement(null, null, "string", null);
candidate.endElement(null, null, "integer"); candidate.characters(value1.toCharArray(), 0, value1.length());
candidate.endElement(null, null, "string");
candidate.startElement(null, null, "key", null);
candidate.characters(attr3.toCharArray(), 0, attr3.length()); candidate.startElement(null, null, "key", null);
candidate.endElement(null, null, "key"); candidate.characters(attr2.toCharArray(), 0, attr2.length());
candidate.startElement(null, null, "true", null); candidate.endElement(null, null, "key");
candidate.endElement(null, null, "true"); candidate.startElement(null, null, "integer", null);
candidate.characters(value2.toCharArray(), 0, value2.length());
candidate.endElement(null, null, "dict"); candidate.endElement(null, null, "integer");
}
candidate.startElement(null, null, "key", null);
candidate.endElement(null, null, "array"); candidate.characters(attr3.toCharArray(), 0, attr3.length());
candidate.endElement(null, null, "key");
candidate.endElement(null, null, "dict"); candidate.startElement(null, null, "true", null);
candidate.endElement(null, null, "plist"); candidate.endElement(null, null, "true");
assertFalse(valueCollector.values.isEmpty()); candidate.endElement(null, null, "dict");
assertTrue(valueCollector.values.size() == 9); }
assertEquals(
"[ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=0, value=val1], " candidate.endElement(null, null, "array");
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=0, value=2], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=0, value=true], " candidate.endElement(null, null, "dict");
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=1, value=val1], " candidate.endElement(null, null, "plist");
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=1, value=2], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=1, value=true], " assertFalse(valueCollector.values.isEmpty());
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=2, value=val1], " assertTrue(valueCollector.values.size() == 9);
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=2, value=2], " assertEquals(
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=2, value=true]]", "[ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=0, value=val1], "
valueCollector.values.toString()); + "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=0, value=2], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=0, value=true], "
assertEquals( + "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=1, value=val1], "
"[attribute.attr1, attribute.attr2, attribute.attr3, attribute.attr1, attribute.attr2, attribute.attr3, attribute.attr1, attribute.attr2, attribute.attr3, attribute]", + "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=1, value=2], "
attrNamesCollector.toString()); + "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=1, value=true], "
} + "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=1, listIndex=2, value=val1], "
+ "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=2, listIndex=2, value=2], "
private static final class ValueCollector implements Consumer<ConfigurationValue> { + "ConfigurationValue [id=null, institutionId=1, configurationId=1, attributeId=3, listIndex=2, value=true]]",
List<ConfigurationValue> values = new ArrayList<>(); valueCollector.values.toString());
@Override assertEquals(
public void accept(final ConfigurationValue value) { "[attribute.attr1, attribute.attr2, attribute.attr3, attribute.attr1, attribute.attr2, attribute.attr3, attribute.attr1, attribute.attr2, attribute.attr3, attribute]",
this.values.add(value); attrNamesCollector.toString());
} }
}
} private static final class ValueCollector implements Consumer<ConfigurationValue> {
List<ConfigurationValue> values = new ArrayList<>();
@Override
public void accept(final ConfigurationValue value) {
this.values.add(value);
}
}
}

View file

@ -1,328 +1,330 @@
/* /*
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * 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/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.converter; package ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.converter;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.junit.Test; import ch.ethz.seb.sebserver.webservice.servicelayer.client.ClientCredentialService;
import org.mockito.Mockito; import org.junit.Test;
import org.mockito.Mockito;
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.AttributeType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationAttribute;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationAttributeDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverter; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationValueDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverterService; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverter;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.AttributeValueConverterService;
public class TableConverterTest {
public class TableConverterTest {
// ********************************
// **** table // ********************************
// ******************************** // **** table
private final ConfigurationAttribute TABLE_ATTR = // ********************************
new ConfigurationAttribute(1L, null, "table", AttributeType.TABLE, null, null, null, null); private final ConfigurationAttribute TABLE_ATTR =
private final ConfigurationValue TABLE_VALUE = new ConfigurationAttribute(1L, null, "table", AttributeType.TABLE, null, null, null, null);
new ConfigurationValue(1L, 1L, 1L, 1L, 0, null); private final ConfigurationValue TABLE_VALUE =
new ConfigurationValue(1L, 1L, 1L, 1L, 0, null);
private final ConfigurationAttribute COLUMN_ATTR_1 =
new ConfigurationAttribute(2L, 1L, "attr1", AttributeType.TEXT_FIELD, null, null, null, null); private final ConfigurationAttribute COLUMN_ATTR_1 =
private final ConfigurationAttribute COLUMN_ATTR_2 = new ConfigurationAttribute(2L, 1L, "attr1", AttributeType.TEXT_FIELD, null, null, null, null);
new ConfigurationAttribute(3L, 1L, "attr2", AttributeType.TEXT_FIELD, null, null, null, null); private final ConfigurationAttribute COLUMN_ATTR_2 =
new ConfigurationAttribute(3L, 1L, "attr2", AttributeType.TEXT_FIELD, null, null, null, null);
private final ConfigurationValue ROW_1_ATTR_1 =
new ConfigurationValue(2L, 1L, 1L, 2L, 0, "1"); private final ConfigurationValue ROW_1_ATTR_1 =
private final ConfigurationValue ROW_1_ATTR_2 = new ConfigurationValue(2L, 1L, 1L, 2L, 0, "1");
new ConfigurationValue(3L, 1L, 1L, 3L, 0, "2"); private final ConfigurationValue ROW_1_ATTR_2 =
new ConfigurationValue(3L, 1L, 1L, 3L, 0, "2");
private final ConfigurationValue ROW_2_ATTR_1 =
new ConfigurationValue(4L, 1L, 1L, 2L, 1, "3"); private final ConfigurationValue ROW_2_ATTR_1 =
private final ConfigurationValue ROW_2_ATTR_2 = new ConfigurationValue(4L, 1L, 1L, 2L, 1, "3");
new ConfigurationValue(5L, 1L, 1L, 3L, 1, "4"); private final ConfigurationValue ROW_2_ATTR_2 =
new ConfigurationValue(5L, 1L, 1L, 3L, 1, "4");
private final Collection<ConfigurationAttribute> TABLE_COLUMNS = Arrays.asList(
this.COLUMN_ATTR_1, private final Collection<ConfigurationAttribute> TABLE_COLUMNS = Arrays.asList(
this.COLUMN_ATTR_2); this.COLUMN_ATTR_1,
this.COLUMN_ATTR_2);
private final List<List<ConfigurationValue>> TABLE_VALUES = Arrays.asList(
Arrays.asList(this.ROW_1_ATTR_1, this.ROW_1_ATTR_2), private final List<List<ConfigurationValue>> TABLE_VALUES = Arrays.asList(
Arrays.asList(this.ROW_2_ATTR_1, this.ROW_2_ATTR_2)); Arrays.asList(this.ROW_1_ATTR_1, this.ROW_1_ATTR_2),
Arrays.asList(this.ROW_2_ATTR_1, this.ROW_2_ATTR_2));
// ********************************
// **** Composite table // ********************************
// ******************************** // **** Composite table
private final ConfigurationAttribute COMPOSITE_TABLE_ATTR = // ********************************
new ConfigurationAttribute(1L, null, "table", AttributeType.COMPOSITE_TABLE, null, null, null, null); private final ConfigurationAttribute COMPOSITE_TABLE_ATTR =
private final ConfigurationValue COMPOSITE_TABLE_VALUE = new ConfigurationAttribute(1L, null, "table", AttributeType.COMPOSITE_TABLE, null, null, null, null);
new ConfigurationValue(1L, 1L, 1L, 1L, 0, null); private final ConfigurationValue COMPOSITE_TABLE_VALUE =
new ConfigurationValue(1L, 1L, 1L, 1L, 0, null);
private final ConfigurationAttribute COMPOSITE_COLUMN_ATTR_1 =
new ConfigurationAttribute(2L, 1L, "attr1", AttributeType.TEXT_FIELD, null, null, null, null); private final ConfigurationAttribute COMPOSITE_COLUMN_ATTR_1 =
private final ConfigurationAttribute COMPOSITE_COLUMN_ATTR_2 = new ConfigurationAttribute(2L, 1L, "attr1", AttributeType.TEXT_FIELD, null, null, null, null);
new ConfigurationAttribute(3L, 1L, "attr2", AttributeType.TEXT_FIELD, null, null, null, null); private final ConfigurationAttribute COMPOSITE_COLUMN_ATTR_2 =
new ConfigurationAttribute(3L, 1L, "attr2", AttributeType.TEXT_FIELD, null, null, null, null);
private final ConfigurationValue COMPOSITE_ROW_1_ATTR_1 =
new ConfigurationValue(2L, 1L, 1L, 2L, 0, "1"); private final ConfigurationValue COMPOSITE_ROW_1_ATTR_1 =
private final ConfigurationValue COMPOSITE_ROW_1_ATTR_2 = new ConfigurationValue(2L, 1L, 1L, 2L, 0, "1");
new ConfigurationValue(3L, 1L, 1L, 3L, 0, "2"); private final ConfigurationValue COMPOSITE_ROW_1_ATTR_2 =
new ConfigurationValue(3L, 1L, 1L, 3L, 0, "2");
private final Collection<ConfigurationAttribute> COMPOSITE_TABLE_ENTRIES = Arrays.asList(
this.COMPOSITE_COLUMN_ATTR_1, private final Collection<ConfigurationAttribute> COMPOSITE_TABLE_ENTRIES = Arrays.asList(
this.COMPOSITE_COLUMN_ATTR_2); this.COMPOSITE_COLUMN_ATTR_1,
this.COMPOSITE_COLUMN_ATTR_2);
private final List<List<ConfigurationValue>> COMPOSITE_TABLE_VALUES = Arrays.asList(
Arrays.asList(this.COMPOSITE_ROW_1_ATTR_1, this.COMPOSITE_ROW_1_ATTR_2)); private final List<List<ConfigurationValue>> COMPOSITE_TABLE_VALUES = Arrays.asList(
Arrays.asList(this.COMPOSITE_ROW_1_ATTR_1, this.COMPOSITE_ROW_1_ATTR_2));
@Test
public void testXMLNormalTable() throws Exception { @Test
public void testXMLNormalTable() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class); final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any())) Mockito.mock(ConfigurationAttributeDAO.class);
.thenReturn(Result.of(this.TABLE_COLUMNS)); Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.TABLE_COLUMNS));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class); final ConfigurationValueDAO configurationValueDAO =
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L)) Mockito.mock(ConfigurationValueDAO.class);
.thenReturn(Result.of(this.TABLE_VALUES)); Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(this.TABLE_VALUES));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService()); final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
final ByteArrayOutputStream out = new ByteArrayOutputStream(); tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToXML(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
tableConverter.convertToXML(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
assertEquals( final String xmlString = new String(out.toByteArray());
"<key>table</key>" assertEquals(
+ "<array>" "<key>table</key>"
+ "<dict>" + "<array>"
+ "<key>attr1</key>" + "<dict>"
+ "<string>1</string>" + "<key>attr1</key>"
+ "<key>attr2</key>" + "<string>1</string>"
+ "<string>2</string>" + "<key>attr2</key>"
+ "</dict>" + "<string>2</string>"
+ "<dict>" + "</dict>"
+ "<key>attr1</key>" + "<dict>"
+ "<string>3</string>" + "<key>attr1</key>"
+ "<key>attr2</key>" + "<string>3</string>"
+ "<string>4</string>" + "<key>attr2</key>"
+ "</dict>" + "<string>4</string>"
+ "</array>", + "</dict>"
xmlString); + "</array>",
xmlString);
}
}
@Test
public void testXMLNormalTableNoValues() throws Exception { @Test
public void testXMLNormalTableNoValues() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class); final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any())) Mockito.mock(ConfigurationAttributeDAO.class);
.thenReturn(Result.of(this.TABLE_COLUMNS)); Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.TABLE_COLUMNS));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class); final ConfigurationValueDAO configurationValueDAO =
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L)) Mockito.mock(ConfigurationValueDAO.class);
.thenReturn(Result.of(Collections.emptyList())); Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(Collections.emptyList()));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService()); final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
final ByteArrayOutputStream out = new ByteArrayOutputStream(); tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToXML(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
tableConverter.convertToXML(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
assertEquals( final String xmlString = new String(out.toByteArray());
"<key>table</key><array />", assertEquals(
xmlString); "<key>table</key><array />",
xmlString);
}
}
@Test
public void testXMLCompositeTable() throws Exception { @Test
public void testXMLCompositeTable() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class); final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any())) Mockito.mock(ConfigurationAttributeDAO.class);
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES)); Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class); final ConfigurationValueDAO configurationValueDAO =
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L)) Mockito.mock(ConfigurationValueDAO.class);
.thenReturn(Result.of(this.COMPOSITE_TABLE_VALUES)); Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(this.COMPOSITE_TABLE_VALUES));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService()); final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
final ByteArrayOutputStream out = new ByteArrayOutputStream(); tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToXML(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
tableConverter.convertToXML(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
assertEquals( final String xmlString = new String(out.toByteArray());
"<key>table</key>" assertEquals(
+ "<dict>" "<key>table</key>"
+ "<key>attr1</key>" + "<dict>"
+ "<string>1</string>" + "<key>attr1</key>"
+ "<key>attr2</key>" + "<string>1</string>"
+ "<string>2</string>" + "<key>attr2</key>"
+ "</dict>", + "<string>2</string>"
xmlString); + "</dict>",
xmlString);
}
}
@Test
public void testXMLCompositeTableEmpty() throws Exception { @Test
public void testXMLCompositeTableEmpty() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class); final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any())) Mockito.mock(ConfigurationAttributeDAO.class);
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES)); Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class); final ConfigurationValueDAO configurationValueDAO =
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L)) Mockito.mock(ConfigurationValueDAO.class);
.thenReturn(Result.of(Collections.emptyList())); Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(Collections.emptyList()));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService()); final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
final ByteArrayOutputStream out = new ByteArrayOutputStream(); tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToXML(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
tableConverter.convertToXML(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
assertEquals( final String xmlString = new String(out.toByteArray());
"", assertEquals(
xmlString); "",
xmlString);
}
}
@Test
public void testJSONNormalTable() throws Exception { @Test
public void testJSONNormalTable() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class); final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any())) Mockito.mock(ConfigurationAttributeDAO.class);
.thenReturn(Result.of(this.TABLE_COLUMNS)); Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.TABLE_COLUMNS));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class); final ConfigurationValueDAO configurationValueDAO =
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L)) Mockito.mock(ConfigurationValueDAO.class);
.thenReturn(Result.of(this.TABLE_VALUES)); Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(this.TABLE_VALUES));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService()); final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
final ByteArrayOutputStream out = new ByteArrayOutputStream(); tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToJSON(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
tableConverter.convertToJSON(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
// expected : "table":[{"attr1":"1","attr2":"2"},{"attr1":"3","attr2":"4"}] final String xmlString = new String(out.toByteArray());
assertEquals( // expected : "table":[{"attr1":"1","attr2":"2"},{"attr1":"3","attr2":"4"}]
"\"table\":[{\"attr1\":\"1\",\"attr2\":\"2\"},{\"attr1\":\"3\",\"attr2\":\"4\"}]", assertEquals(
xmlString); "\"table\":[{\"attr1\":\"1\",\"attr2\":\"2\"},{\"attr1\":\"3\",\"attr2\":\"4\"}]",
xmlString);
}
}
@Test
public void testJSONNormalTableEmpty() throws Exception { @Test
public void testJSONNormalTableEmpty() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class); final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any())) Mockito.mock(ConfigurationAttributeDAO.class);
.thenReturn(Result.of(this.TABLE_COLUMNS)); Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.TABLE_COLUMNS));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class); final ConfigurationValueDAO configurationValueDAO =
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L)) Mockito.mock(ConfigurationValueDAO.class);
.thenReturn(Result.of(Collections.emptyList())); Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(Collections.emptyList()));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService()); final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
final ByteArrayOutputStream out = new ByteArrayOutputStream(); tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToJSON(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
tableConverter.convertToJSON(out, this.TABLE_ATTR, attr -> this.TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
// expected : "table":[] final String xmlString = new String(out.toByteArray());
assertEquals( // expected : "table":[]
"\"table\":[]", assertEquals(
xmlString); "\"table\":[]",
xmlString);
}
}
@Test
public void testJSONCompositeTable() throws Exception { @Test
public void testJSONCompositeTable() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class); final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any())) Mockito.mock(ConfigurationAttributeDAO.class);
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES)); Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class); final ConfigurationValueDAO configurationValueDAO =
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L)) Mockito.mock(ConfigurationValueDAO.class);
.thenReturn(Result.of(this.COMPOSITE_TABLE_VALUES)); Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(this.COMPOSITE_TABLE_VALUES));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService()); final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
final ByteArrayOutputStream out = new ByteArrayOutputStream(); tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToJSON(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
tableConverter.convertToJSON(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
// expected : "table":{"attr1":"1","attr2":"2"} final String xmlString = new String(out.toByteArray());
assertEquals( // expected : "table":{"attr1":"1","attr2":"2"}
"\"table\":{\"attr1\":\"1\",\"attr2\":\"2\"}", assertEquals(
xmlString); "\"table\":{\"attr1\":\"1\",\"attr2\":\"2\"}",
xmlString);
}
}
@Test
public void testJSONCompositeTableEmpty() throws Exception { @Test
public void testJSONCompositeTableEmpty() throws Exception {
final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.mock(ConfigurationAttributeDAO.class); final ConfigurationAttributeDAO configurationAttributeDAO =
Mockito.when(configurationAttributeDAO.allMatching(Mockito.any())) Mockito.mock(ConfigurationAttributeDAO.class);
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES)); Mockito.when(configurationAttributeDAO.allMatching(Mockito.any()))
.thenReturn(Result.of(this.COMPOSITE_TABLE_ENTRIES));
final ConfigurationValueDAO configurationValueDAO =
Mockito.mock(ConfigurationValueDAO.class); final ConfigurationValueDAO configurationValueDAO =
Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L)) Mockito.mock(ConfigurationValueDAO.class);
.thenReturn(Result.of(Collections.emptyList())); Mockito.when(configurationValueDAO.getOrderedTableValues(1L, 1L, 1L))
.thenReturn(Result.of(Collections.emptyList()));
final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
tableConverter.init(createAttributeValueConverterService()); final TableConverter tableConverter = new TableConverter(configurationAttributeDAO, configurationValueDAO);
final ByteArrayOutputStream out = new ByteArrayOutputStream(); tableConverter.init(createAttributeValueConverterService());
final ByteArrayOutputStream out = new ByteArrayOutputStream();
tableConverter.convertToJSON(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
tableConverter.convertToJSON(out, this.COMPOSITE_TABLE_ATTR, attr -> this.COMPOSITE_TABLE_VALUE);
final String xmlString = new String(out.toByteArray());
// expected : final String xmlString = new String(out.toByteArray());
assertEquals( // expected :
"", assertEquals(
xmlString); "",
xmlString);
}
}
private AttributeValueConverterService createAttributeValueConverterService() {
final List<AttributeValueConverter> converter = new ArrayList<>(); private AttributeValueConverterService createAttributeValueConverterService() {
converter.add(new StringConverter()); final ClientCredentialService clientCredentialServiceMock = Mockito.mock(ClientCredentialService.class);
return new AttributeValueConverterServiceImpl(converter); final List<AttributeValueConverter> converter = new ArrayList<>();
} converter.add(new StringConverter(clientCredentialServiceMock));
return new AttributeValueConverterServiceImpl(converter);
} }
}