SEBSERV-107 fixes

This commit is contained in:
anhefti 2020-03-25 16:09:05 +01:00
parent ceb7308f30
commit 3d20038a1e
19 changed files with 122 additions and 78 deletions

View file

@ -29,22 +29,58 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gbl.util.Utils;
/** A POST parameter mapper that wraps all parameter from a POST request given by a MultiValueMap<String, String> and
* defines API specific convenience functions to access this parameter with given type and conversion of needed. */
* defines API specific convenience functions to access this parameter with given type and conversion of needed. */
public class POSTMapper {
public static final POSTMapper EMPTY_MAP = new POSTMapper(null);
public static final POSTMapper EMPTY_MAP = new POSTMapper(null, null);
protected final MultiValueMap<String, String> params;
public POSTMapper(final MultiValueMap<String, String> params) {
public POSTMapper(final MultiValueMap<String, String> params, final String uriQueryString) {
super();
this.params = params != null
? new LinkedMultiValueMap<>(params)
: new LinkedMultiValueMap<>();
if (uriQueryString != null) {
handleEncodedURIParams(uriQueryString);
}
}
// NOTE: this is a workaround since URI parameter are not automatically decoded in the HTTPServletRequest
// while parameter from form-urlencoded body part are.
// I also tried to set application property: server.tomcat.uri-encoding=UTF-8 bit with no effect.
// TODO Didn't found a better solution for now but if there is some time, we should find a better solution
private void handleEncodedURIParams(final String uriQueryString) {
final MultiValueMap<String, String> override = new LinkedMultiValueMap<>();
this.params
.entrySet()
.stream()
.forEach(entry -> {
if (uriQueryString.contains(entry.getKey())) {
override.put(
entry.getKey(),
entry.getValue().stream()
.map(val -> decode(val))
.collect(Collectors.toList()));
}
});
if (!override.isEmpty()) {
this.params.putAll(override);
}
}
private String decode(final String val) {
try {
return Utils.decodeFormURL_UTF_8(val);
} catch (final Exception e) {
return val;
}
}
public String getString(final String name) {
return Utils.decodeFormURL_UTF_8(this.params.getFirst(name));
return this.params.getFirst(name);
}
public char[] getCharArray(final String name) {

View file

@ -475,7 +475,7 @@ public final class Utils {
}
public static String toSQLWildcard(final String text) {
return (text == null) ? null : Constants.PERCENTAGE + text + Constants.PERCENTAGE;
return (text == null) ? null : Constants.PERCENTAGE + text.replace("%", "\\%") + Constants.PERCENTAGE;
}
public static String hash_SHA_256_Base_16(final CharSequence chars) {
@ -487,7 +487,7 @@ public final class Utils {
final MessageDigest digest = MessageDigest.getInstance(Constants.SHA_256);
final byte[] encodedHash = digest.digest(toByteArray(chars));
return Hex.encodeHexString(encodedHash);
} catch (NoSuchAlgorithmException e) {
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to hash text: ", e);
}
}

View file

@ -8,18 +8,14 @@
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -36,13 +32,19 @@ import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
public abstract class RestCall<T> {
@ -291,7 +293,7 @@ public abstract class RestCall<T> {
}
public RestCallBuilder withQueryParam(final String name, final String value) {
this.queryParams.put(name, Arrays.asList(value));
this.queryParams.add(name, value);
return this;
}

View file

@ -110,24 +110,4 @@ public class RestServiceImpl implements RestService {
return restCall.newBuilder();
}
// @Override
// public <T> PageAction activation(final PageAction action) {
// if (action.restCallType() == null) {
// throw new IllegalArgumentException("ActionDefinition needs to define a restCallType to use this action");
// }
//
// @SuppressWarnings("unchecked")
// final Class<? extends RestCall<T>> restCallType =
// (Class<? extends RestCall<T>>) action.restCallType();
//
// this.getBuilder(restCallType)
// .withURIVariable(
// API.PARAM_MODEL_ID,
// action.pageContext().getAttribute(AttributeKeys.ENTITY_ID))
// .call()
// .onErrorDo(t -> action.pageContext().notifyError(t));
//
// return action;
// }
}

View file

@ -46,11 +46,11 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
public class FilterMap extends POSTMapper {
public FilterMap() {
super(new LinkedMultiValueMap<>());
super(new LinkedMultiValueMap<>(), null);
}
public FilterMap(final MultiValueMap<String, String> params) {
super(params);
public FilterMap(final MultiValueMap<String, String> params, final String uriQueryString) {
super(params, uriQueryString);
}
public Integer getActiveAsInt() {
@ -309,7 +309,7 @@ public class FilterMap extends POSTMapper {
}
public FilterMap create() {
return new FilterMap(this.filterMap.params);
return new FilterMap(this.filterMap.params, null);
}
}

View file

@ -16,6 +16,7 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
@ -23,7 +24,6 @@ import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import org.joda.time.DateTimeZone;
/** Defines the LMS API access service interface with all functionality needed to access
* a LMS API within a given LmsSetup configuration.
@ -107,7 +107,7 @@ public interface LmsAPIService {
final boolean startTimeFilter =
(from == null) || (q.startTime != null && (q.startTime.isEqual(from) || q.startTime.isAfter(from)));
final boolean currentlyRunning = DateTime.now(DateTimeZone.UTC).isBefore(q.endTime);
return nameFilter && (startTimeFilter || currentlyRunning) ;
return nameFilter && (startTimeFilter || currentlyRunning);
};
}
@ -149,7 +149,7 @@ public interface LmsAPIService {
}
return new Page<>(
(quizzes.size() / pageSize),
quizzes.size() / pageSize + 1,
pageNumber,
sortAttribute,
quizzes.subList(start, end));

View file

@ -10,6 +10,8 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
@ -82,12 +84,13 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
@RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
@RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
@RequestParam final MultiValueMap<String, String> allRequestParams) {
@RequestParam final MultiValueMap<String, String> allRequestParams,
final HttpServletRequest request) {
// at least current user must have base read access for specified entity type within its own institution
checkReadPrivilege(institutionId);
final FilterMap filterMap = new FilterMap(allRequestParams);
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
// if current user has no read access for specified entity type within other institution
// then the current users institutionId is put as a SQL filter criteria attribute to extends query performance
@ -104,7 +107,7 @@ public class ClientEventController extends ReadonlyEntityController<ClientEvent,
getSQLTableOfEntity().name(),
() -> this.clientEventDAO.allMatchingExtended(filterMap, this::hasReadAccess))
.getOrThrow();
} catch (Exception e) {
} catch (final Exception e) {
e.printStackTrace();
throw e;
}

View file

@ -332,12 +332,13 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
@RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
@RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
@RequestParam final MultiValueMap<String, String> allRequestParams) {
@RequestParam final MultiValueMap<String, String> allRequestParams,
final HttpServletRequest request) {
// at least current user must have read access for specified entity type within its own institution
checkReadPrivilege(institutionId);
final FilterMap filterMap = new FilterMap(allRequestParams);
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
// if current user has no read access for specified entity type within other institution
// then the current users institutionId is put as a SQL filter criteria attribute to extends query performance

View file

@ -13,6 +13,7 @@ import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.apache.commons.lang3.StringUtils;
@ -127,12 +128,13 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
@RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
@RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
@RequestParam final MultiValueMap<String, String> allRequestParams) {
@RequestParam final MultiValueMap<String, String> allRequestParams,
final HttpServletRequest request) {
// at least current user must have read access for specified entity type within its own institution
checkReadPrivilege(institutionId);
final FilterMap filterMap = new FilterMap(allRequestParams);
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
// if current user has no read access for specified entity type within other institution
// then the current users institutionId is put as a SQL filter criteria attribute to extends query performance
@ -163,12 +165,13 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@RequestParam final MultiValueMap<String, String> allRequestParams) {
@RequestParam final MultiValueMap<String, String> allRequestParams,
final HttpServletRequest request) {
// at least current user must have read access for specified entity type within its own institution
checkReadPrivilege(institutionId);
final FilterMap filterMap = new FilterMap(allRequestParams);
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
// if current user has no read access for specified entity type within other institution then its own institution,
// then the current users institutionId is put as a SQL filter criteria attribute to extends query performance
@ -262,12 +265,13 @@ public abstract class EntityController<T extends Entity, M extends Entity> {
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
final HttpServletRequest request) {
// check modify privilege for requested institution and concrete entityType
this.checkModifyPrivilege(institutionId);
final POSTMapper postMap = new POSTMapper(allRequestParams)
final POSTMapper postMap = new POSTMapper(allRequestParams, request.getQueryString())
.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
final M requestModel = this.createNew(postMap);

View file

@ -98,7 +98,7 @@ public class ExamAPI_V1_Controller {
return CompletableFuture.supplyAsync(
() -> {
final POSTMapper mapper = new POSTMapper(formParams);
final POSTMapper mapper = new POSTMapper(formParams, request.getQueryString());
final String remoteAddr = request.getRemoteAddr();
final Long institutionId = (instIdRequestParam != null)

View file

@ -18,6 +18,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
@ -135,7 +136,8 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
@RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
@RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
@RequestParam final MultiValueMap<String, String> allRequestParams) {
@RequestParam final MultiValueMap<String, String> allRequestParams,
final HttpServletRequest request) {
checkReadPrivilege(institutionId);
@ -145,7 +147,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
if (StringUtils.isBlank(sort) ||
this.paginationService.isNativeSortingSupported(ExamRecordDynamicSqlSupport.examRecord, sort)) {
return super.getPage(institutionId, pageNumber, pageSize, sort, allRequestParams);
return super.getPage(institutionId, pageNumber, pageSize, sort, allRequestParams, request);
} else {
@ -156,7 +158,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
final List<Exam> exams = new ArrayList<>(
this.examDAO
.allMatching(new FilterMap(allRequestParams), this::hasReadAccess)
.allMatching(new FilterMap(allRequestParams, request.getQueryString()), this::hasReadAccess)
.getOrThrow());
return buildSortedExamPage(

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import javax.servlet.http.HttpServletRequest;
import org.mybatis.dynamic.sql.SqlTable;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
@ -135,11 +137,12 @@ public class ExamConfigurationMappingController extends EntityController<ExamCon
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
final HttpServletRequest request) {
// check modify privilege for requested institution and concrete entityType
this.checkModifyPrivilege(institutionId);
final POSTMapper postMap = new POSTMapper(allRequestParams)
final POSTMapper postMap = new POSTMapper(allRequestParams, request.getQueryString())
.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
final ExamConfigurationMap requestModel = this.createNew(postMap);

View file

@ -14,6 +14,7 @@ import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.apache.commons.lang3.StringUtils;
@ -119,14 +120,15 @@ public class ExamMonitoringController {
@RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
@RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
@RequestParam final MultiValueMap<String, String> allRequestParams) {
@RequestParam final MultiValueMap<String, String> allRequestParams,
final HttpServletRequest request) {
this.authorization.checkRole(
institutionId,
EntityType.EXAM,
UserRole.EXAM_SUPPORTER);
final FilterMap filterMap = new FilterMap(allRequestParams);
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
// if current user has no read access for specified entity type within other institution
// then the current users institutionId is put as a SQL filter criteria attribute to extends query performance

View file

@ -8,6 +8,8 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
@ -78,14 +80,15 @@ public class QuizController {
@RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
@RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
@RequestParam final MultiValueMap<String, String> allRequestParams) {
@RequestParam final MultiValueMap<String, String> allRequestParams,
final HttpServletRequest request) {
this.authorization.check(
PrivilegeType.READ,
EntityType.EXAM,
institutionId);
final FilterMap filterMap = new FilterMap(allRequestParams);
final FilterMap filterMap = new FilterMap(allRequestParams, request.getQueryString());
// if current user has no read access for specified entity type within other institution
// then the current users institutionId is put as a SQL filter criteria attribute to extends query performance
if (!this.authorization.hasGrant(PrivilegeType.READ, EntityType.EXAM)) {

View file

@ -8,6 +8,7 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.springframework.util.MultiValueMap;
@ -52,7 +53,8 @@ public abstract class ReadonlyEntityController<T extends Entity, M extends Entit
}
@Override
public T create(final MultiValueMap<String, String> allRequestParams, final Long institutionId) {
public T create(final MultiValueMap<String, String> allRequestParams, final Long institutionId,
final HttpServletRequest request) {
throw new UnsupportedOperationException(ONLY_READ_ACCESS);
}

View file

@ -11,6 +11,8 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.ArrayList;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.password.PasswordEncoder;
@ -62,9 +64,11 @@ public class RegisterUserController {
method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public UserInfo registerNewUser(@RequestParam final MultiValueMap<String, String> allRequestParams) {
public UserInfo registerNewUser(
@RequestParam final MultiValueMap<String, String> allRequestParams,
final HttpServletRequest request) {
final POSTMapper postMap = new POSTMapper(allRequestParams)
final POSTMapper postMap = new POSTMapper(allRequestParams, request.getQueryString())
.putIfAbsent(USER_ROLE.REFERENCE_NAME, UserRole.EXAM_SUPPORTER.name());
final UserMod userMod = new UserMod(null, postMap);

View file

@ -3,4 +3,5 @@ spring.profiles.include=dev-ws,dev-gui
server.address=localhost
server.port=8080
server.servlet.context-path=/
server.tomcat.uri-encoding=UTF-8

View file

@ -15,7 +15,7 @@ server.servlet.context-path=/
# Tomcat
server.tomcat.max-threads=1000
server.tomcat.accept-count=300
server.tomcat.uri-encoding=UTF-8
### encoding
file.encoding=UTF-8

View file

@ -34,6 +34,7 @@ public class ClientConfigTest extends GuiIntegrationTest {
final Result<SebClientConfig> call = restService.getBuilder(NewClientConfig.class)
.withQueryParam(Domain.SEB_CLIENT_CONFIGURATION.ATTR_NAME, "new client config")
.withFormParam("Test", "new client config")
.withFormParam(SebClientConfig.ATTR_CONFIG_PURPOSE, SebClientConfig.ConfigPurpose.START_EXAM.name())
.call();