Merge branch 'master' of ssh://dev.hostsharing.net:29418/hsadmin/hsadmin-ng
This commit is contained in:
commit
7592a1d459
17
build.gradle
17
build.gradle
@ -154,6 +154,11 @@ def jhipsterGeneratedClassesWithLowCoverage = [
|
|||||||
'*_'
|
'*_'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def specialExceptions = [
|
||||||
|
// lots of unreachable code due to error handling / verifications
|
||||||
|
'org.hostsharing.hsadminng.service.accessfilter.JSonAccessFilter'
|
||||||
|
]
|
||||||
|
|
||||||
jacocoTestCoverageVerification {
|
jacocoTestCoverageVerification {
|
||||||
violationRules {
|
violationRules {
|
||||||
rule {
|
rule {
|
||||||
@ -165,7 +170,7 @@ jacocoTestCoverageVerification {
|
|||||||
// Keep in mind, git will blame you ;-)
|
// Keep in mind, git will blame you ;-)
|
||||||
minimum = 0.95
|
minimum = 0.95
|
||||||
}
|
}
|
||||||
excludes = jhipsterGeneratedClassesWithDecentCoverage + jhipsterGeneratedClassesWithLowCoverage
|
excludes = jhipsterGeneratedClassesWithDecentCoverage + jhipsterGeneratedClassesWithLowCoverage + specialExceptions
|
||||||
}
|
}
|
||||||
|
|
||||||
rule {
|
rule {
|
||||||
@ -177,6 +182,16 @@ jacocoTestCoverageVerification {
|
|||||||
}
|
}
|
||||||
includes = jhipsterGeneratedClassesWithDecentCoverage
|
includes = jhipsterGeneratedClassesWithDecentCoverage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rule {
|
||||||
|
element = 'CLASS'
|
||||||
|
limit {
|
||||||
|
counter = 'LINE'
|
||||||
|
value = 'COVEREDRATIO'
|
||||||
|
minimum = 0.90
|
||||||
|
}
|
||||||
|
includes = specialExceptions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,10 +28,6 @@ abstract class JSonAccessFilter<T> {
|
|||||||
this.parentIdField = determineFieldWithAnnotation(dto.getClass(), ParentId.class);
|
this.parentIdField = determineFieldWithAnnotation(dto.getClass(), ParentId.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isParentIdField(final Field field) {
|
|
||||||
return field.equals(parentIdField);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long getId() {
|
Long getId() {
|
||||||
if (selfIdField == null) {
|
if (selfIdField == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -95,7 +91,7 @@ abstract class JSonAccessFilter<T> {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected Object loadDto(final Class<? extends IdToDtoResolver> resolverClass, final Long id) {
|
protected Object loadDto(final Class<? extends IdToDtoResolver> resolverClass, final Long id) {
|
||||||
verify(id != null, "id must not be null");
|
verify(id != null, "id must not be null for " + resolverClass.getSimpleName());
|
||||||
|
|
||||||
final AutowireCapableBeanFactory beanFactory = ctx.getAutowireCapableBeanFactory();
|
final AutowireCapableBeanFactory beanFactory = ctx.getAutowireCapableBeanFactory();
|
||||||
verify(beanFactory != null, "no bean factory found, probably missing mock configuration for ApplicationContext, e.g. given(...)");
|
verify(beanFactory != null, "no bean factory found, probably missing mock configuration for ApplicationContext, e.g. given(...)");
|
||||||
|
@ -11,6 +11,7 @@ import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException;
|
|||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -40,8 +41,8 @@ public class JSonDeserializerWithAccessFilter<T> extends JSonAccessFilter<T> {
|
|||||||
treeNode.fieldNames().forEachRemaining(fieldName -> {
|
treeNode.fieldNames().forEachRemaining(fieldName -> {
|
||||||
try {
|
try {
|
||||||
final Field field = dto.getClass().getDeclaredField(fieldName);
|
final Field field = dto.getClass().getDeclaredField(fieldName);
|
||||||
final Object newValue = readValue(treeNode, field);
|
final Object newValue = readValueFromJSon(treeNode, field);
|
||||||
writeValue(dto, field, newValue);
|
writeValueToDto(dto, field, newValue);
|
||||||
} catch (NoSuchFieldException e) {
|
} catch (NoSuchFieldException e) {
|
||||||
throw new RuntimeException("setting field " + fieldName + " failed", e);
|
throw new RuntimeException("setting field " + fieldName + " failed", e);
|
||||||
}
|
}
|
||||||
@ -69,12 +70,12 @@ public class JSonDeserializerWithAccessFilter<T> extends JSonAccessFilter<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object readValue(final TreeNode treeNode, final Field field) {
|
private Object readValueFromJSon(final TreeNode treeNode, final Field field) {
|
||||||
return readValue(treeNode, field.getName(), field.getType());
|
return readValueFromJSon(treeNode, field.getName(), field.getType());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object readValue(final TreeNode treeNode, final String fieldName, final Class<?> fieldClass) {
|
private Object readValueFromJSon(final TreeNode treeNode, final String fieldName, final Class<?> fieldClass) {
|
||||||
final TreeNode fieldNode = treeNode.get(fieldName);
|
final TreeNode fieldNode = treeNode.get(fieldName);
|
||||||
if (fieldNode instanceof NullNode) {
|
if (fieldNode instanceof NullNode) {
|
||||||
return null;
|
return null;
|
||||||
@ -88,23 +89,29 @@ public class JSonDeserializerWithAccessFilter<T> extends JSonAccessFilter<T> {
|
|||||||
if (fieldNode instanceof LongNode) {
|
if (fieldNode instanceof LongNode) {
|
||||||
return ((LongNode) fieldNode).asLong();
|
return ((LongNode) fieldNode).asLong();
|
||||||
}
|
}
|
||||||
|
if (fieldNode instanceof DoubleNode) {
|
||||||
|
// TODO: we need to figure out, why DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS does not work
|
||||||
|
return ((DoubleNode) fieldNode).asDouble();
|
||||||
|
}
|
||||||
if (fieldNode instanceof ArrayNode && LocalDate.class.isAssignableFrom(fieldClass)) {
|
if (fieldNode instanceof ArrayNode && LocalDate.class.isAssignableFrom(fieldClass)) {
|
||||||
return LocalDate.of(((ArrayNode) fieldNode).get(0).asInt(), ((ArrayNode) fieldNode).get(1).asInt(), ((ArrayNode) fieldNode).get(2).asInt());
|
return LocalDate.of(((ArrayNode) fieldNode).get(0).asInt(), ((ArrayNode) fieldNode).get(1).asInt(), ((ArrayNode) fieldNode).get(2).asInt());
|
||||||
}
|
}
|
||||||
{
|
throw new NotImplementedException("JSon node type not implemented: " + fieldNode.getClass() + " -> " + fieldName + ": " + fieldClass);
|
||||||
throw new NotImplementedException("property type not yet implemented: " + fieldNode + " -> " + fieldName + ": " + fieldClass);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeValue(final T dto, final Field field, final Object value) {
|
private void writeValueToDto(final T dto, final Field field, final Object value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
ReflectionUtil.setValue(dto, field, null);
|
ReflectionUtil.setValue(dto, field, null);
|
||||||
} else if (field.getType().isAssignableFrom(value.getClass())) {
|
} else if (field.getType().isAssignableFrom(value.getClass())) {
|
||||||
ReflectionUtil.setValue(dto, field, value);
|
ReflectionUtil.setValue(dto, field, value);
|
||||||
} else if (Integer.class.isAssignableFrom(field.getType()) || int.class.isAssignableFrom(field.getType())) {
|
} else if (int.class.isAssignableFrom(field.getType())) {
|
||||||
ReflectionUtil.setValue(dto, field, ((Number) value).intValue());
|
ReflectionUtil.setValue(dto, field, ((Number) value).intValue());
|
||||||
} else if (Long.class.isAssignableFrom(field.getType()) || long.class.isAssignableFrom(field.getType())) {
|
} else if (Long.class.isAssignableFrom(field.getType()) || long.class.isAssignableFrom(field.getType())) {
|
||||||
ReflectionUtil.setValue(dto, field, ((Number) value).longValue());
|
ReflectionUtil.setValue(dto, field, ((Number) value).longValue());
|
||||||
|
} else if (BigDecimal.class.isAssignableFrom(field.getType())) {
|
||||||
|
ReflectionUtil.setValue(dto, field, new BigDecimal(value.toString()));
|
||||||
|
} else if (Boolean.class.isAssignableFrom(field.getType()) || boolean.class.isAssignableFrom(field.getType())) {
|
||||||
|
ReflectionUtil.setValue(dto, field, Boolean.valueOf(value.toString()));
|
||||||
} else if (field.getType().isEnum()) {
|
} else if (field.getType().isEnum()) {
|
||||||
ReflectionUtil.setValue(dto, field, Enum.valueOf((Class<Enum>) field.getType(), value.toString()));
|
ReflectionUtil.setValue(dto, field, Enum.valueOf((Class<Enum>) field.getType(), value.toString()));
|
||||||
} else if (LocalDate.class.isAssignableFrom(field.getType())) {
|
} else if (LocalDate.class.isAssignableFrom(field.getType())) {
|
||||||
|
@ -4,10 +4,12 @@ package org.hostsharing.hsadminng.service.accessfilter;
|
|||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
|
import org.hostsharing.hsadminng.service.util.ReflectionUtil;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
|
||||||
public class JSonSerializerWithAccessFilter<T> extends JSonAccessFilter<T> {
|
public class JSonSerializerWithAccessFilter<T> extends JSonAccessFilter<T> {
|
||||||
@ -41,32 +43,27 @@ public class JSonSerializerWithAccessFilter<T> extends JSonAccessFilter<T> {
|
|||||||
// Alternatively extract the supported types to subclasses of some abstract class and
|
// Alternatively extract the supported types to subclasses of some abstract class and
|
||||||
// here as well as in the deserializer just access the matching implementation through a map.
|
// here as well as in the deserializer just access the matching implementation through a map.
|
||||||
// Or even completely switch from Jackson to GSON?
|
// Or even completely switch from Jackson to GSON?
|
||||||
final Object fieldValue = get(dto, field);
|
final Object fieldValue = ReflectionUtil.getValue(dto, field);
|
||||||
if (fieldValue == null) {
|
if (fieldValue == null) {
|
||||||
jsonGenerator.writeNullField(fieldName);
|
jsonGenerator.writeNullField(fieldName);
|
||||||
|
} else if (String.class.isAssignableFrom(field.getType())) {
|
||||||
|
jsonGenerator.writeStringField(fieldName, (String) fieldValue);
|
||||||
} else if (Integer.class.isAssignableFrom(field.getType()) || int.class.isAssignableFrom(field.getType())) {
|
} else if (Integer.class.isAssignableFrom(field.getType()) || int.class.isAssignableFrom(field.getType())) {
|
||||||
jsonGenerator.writeNumberField(fieldName, (int) fieldValue);
|
jsonGenerator.writeNumberField(fieldName, (int) fieldValue);
|
||||||
} else if (Long.class.isAssignableFrom(field.getType()) || long.class.isAssignableFrom(field.getType())) {
|
} else if (Long.class.isAssignableFrom(field.getType()) || long.class.isAssignableFrom(field.getType())) {
|
||||||
jsonGenerator.writeNumberField(fieldName, (long) fieldValue);
|
jsonGenerator.writeNumberField(fieldName, (long) fieldValue);
|
||||||
} else if (LocalDate.class.isAssignableFrom(field.getType())) {
|
} else if (LocalDate.class.isAssignableFrom(field.getType())) {
|
||||||
jsonGenerator.writeStringField(fieldName, fieldValue.toString()); // TODO proper format
|
jsonGenerator.writeStringField(fieldName, fieldValue.toString());
|
||||||
} else if (Enum.class.isAssignableFrom(field.getType())) {
|
} else if (Enum.class.isAssignableFrom(field.getType())) {
|
||||||
jsonGenerator.writeStringField(fieldName, fieldValue.toString()); // TODO proper representation
|
jsonGenerator.writeStringField(fieldName, ((Enum) fieldValue).name());
|
||||||
} else if (String.class.isAssignableFrom(field.getType())) {
|
} else if (Boolean.class.isAssignableFrom(field.getType()) || boolean.class.isAssignableFrom(field.getType())) {
|
||||||
jsonGenerator.writeStringField(fieldName, (String) fieldValue);
|
jsonGenerator.writeBooleanField(fieldName, (Boolean) fieldValue);
|
||||||
|
} else if (BigDecimal.class.isAssignableFrom(field.getType())) {
|
||||||
|
jsonGenerator.writeNumberField(fieldName, (BigDecimal) fieldValue);
|
||||||
} else {
|
} else {
|
||||||
throw new NotImplementedException("property type not yet implemented: " + field);
|
throw new NotImplementedException("property type not yet implemented: " + field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object get(final Object dto, final Field field) {
|
|
||||||
try {
|
|
||||||
field.setAccessible(true);
|
|
||||||
return field.get(dto);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException("getting field " + field + " failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,9 @@ spring:
|
|||||||
enabled: false
|
enabled: false
|
||||||
thymeleaf:
|
thymeleaf:
|
||||||
mode: HTML
|
mode: HTML
|
||||||
|
jackson:
|
||||||
|
deserialization:
|
||||||
|
USE_BIG_DECIMAL_FOR_FLOATS: true
|
||||||
|
|
||||||
server:
|
server:
|
||||||
servlet:
|
servlet:
|
||||||
|
@ -0,0 +1,144 @@
|
|||||||
|
package org.hostsharing.hsadminng.service.accessfilter;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
import org.apache.commons.lang3.RandomUtils;
|
||||||
|
import org.hostsharing.hsadminng.service.IdToDtoResolver;
|
||||||
|
import org.hostsharing.hsadminng.service.dto.FluentBuilder;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
import static org.hostsharing.hsadminng.service.accessfilter.Role.*;
|
||||||
|
|
||||||
|
public class JSonAccessFilterTestFixture {
|
||||||
|
|
||||||
|
static GivenDto createSampleDto() {
|
||||||
|
final GivenDto dto = new GivenDto();
|
||||||
|
dto.customerId = 888L;
|
||||||
|
dto.restrictedField = RandomStringUtils.randomAlphabetic(10);
|
||||||
|
dto.openStringField = RandomStringUtils.randomAlphabetic(10);
|
||||||
|
dto.openIntegerField = RandomUtils.nextInt();
|
||||||
|
dto.openPrimitiveIntField = RandomUtils.nextInt();
|
||||||
|
dto.openLongField = RandomUtils.nextLong();
|
||||||
|
dto.openPrimitiveLongField = RandomUtils.nextLong();
|
||||||
|
dto.openBooleanField = true;
|
||||||
|
dto.openPrimitiveBooleanField = false;
|
||||||
|
dto.openBigDecimalField = new BigDecimal("987654321234567890987654321234567890.09");
|
||||||
|
dto.openLocalDateField = LocalDate.parse("2019-04-25");
|
||||||
|
dto.openLocalDateFieldAsString = "2019-04-25";
|
||||||
|
dto.openEnumField = TestEnum.GREEN;
|
||||||
|
dto.openEnumFieldAsString = "GREEN";
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class GivenCustomerDto extends FluentBuilder<GivenCustomerDto> {
|
||||||
|
@SelfId(resolver = GivenService.class)
|
||||||
|
@AccessFor(read = ANYBODY)
|
||||||
|
Long id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static abstract class GivenCustomerService implements IdToDtoResolver<GivenCustomerDto> {
|
||||||
|
}
|
||||||
|
|
||||||
|
static class GivenDto extends FluentBuilder<GivenDto> {
|
||||||
|
@SelfId(resolver = GivenService.class)
|
||||||
|
@AccessFor(read = ANYBODY)
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
@ParentId(resolver = GivenCustomerService.class)
|
||||||
|
@AccessFor(init = ACTUAL_CUSTOMER_USER, update = ACTUAL_CUSTOMER_USER, read = ACTUAL_CUSTOMER_USER)
|
||||||
|
Long customerId;
|
||||||
|
|
||||||
|
@AccessFor(init = {TECHNICAL_CONTACT, FINANCIAL_CONTACT}, update = {TECHNICAL_CONTACT, FINANCIAL_CONTACT}, read = {TECHNICAL_CONTACT, FINANCIAL_CONTACT})
|
||||||
|
String restrictedField;
|
||||||
|
|
||||||
|
@AccessFor(init = ANYBODY, update = ANYBODY, read = ANYBODY)
|
||||||
|
String openStringField;
|
||||||
|
|
||||||
|
@AccessFor(init = ANYBODY, update = ANYBODY, read = ANYBODY)
|
||||||
|
Integer openIntegerField;
|
||||||
|
|
||||||
|
@AccessFor(init = ANYBODY, update = ANYBODY, read = ANYBODY)
|
||||||
|
int openPrimitiveIntField;
|
||||||
|
|
||||||
|
@AccessFor(init = ANYBODY, update = ANYBODY, read = ANYBODY)
|
||||||
|
Long openLongField;
|
||||||
|
|
||||||
|
@AccessFor(init = ANYBODY, update = ANYBODY, read = ANYBODY)
|
||||||
|
long openPrimitiveLongField;
|
||||||
|
|
||||||
|
@AccessFor(init = ANYBODY, update = ANYBODY, read = ANYBODY)
|
||||||
|
Boolean openBooleanField;
|
||||||
|
|
||||||
|
@AccessFor(read = ANYBODY)
|
||||||
|
boolean openPrimitiveBooleanField;
|
||||||
|
|
||||||
|
@AccessFor(init = ANYBODY, update = ANYBODY, read = ANYBODY)
|
||||||
|
LocalDate openLocalDateField;
|
||||||
|
transient String openLocalDateFieldAsString;
|
||||||
|
|
||||||
|
@AccessFor(init = ANYBODY, update = ANYBODY, read = ANYBODY)
|
||||||
|
LocalDate openLocalDateField2;
|
||||||
|
transient String openLocalDateField2AsString;
|
||||||
|
|
||||||
|
@AccessFor(init = ANYBODY, update = ANYBODY, read = ANYBODY)
|
||||||
|
TestEnum openEnumField;
|
||||||
|
transient String openEnumFieldAsString;
|
||||||
|
|
||||||
|
@AccessFor(init = ANYBODY, update = ANYBODY, read = ANYBODY)
|
||||||
|
BigDecimal openBigDecimalField;
|
||||||
|
|
||||||
|
@AccessFor(init = ANYBODY, update = ANYBODY, read = ANYBODY)
|
||||||
|
int[] openArrayField;
|
||||||
|
}
|
||||||
|
|
||||||
|
static abstract class GivenService implements IdToDtoResolver<GivenDto> {
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TestEnum {
|
||||||
|
BLUE, GREEN
|
||||||
|
}
|
||||||
|
|
||||||
|
static abstract class GivenChildService implements IdToDtoResolver<GivenChildDto> {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GivenChildDto extends FluentBuilder<GivenChildDto> {
|
||||||
|
|
||||||
|
@SelfId(resolver = GivenChildService.class)
|
||||||
|
@AccessFor(read = Role.ANY_CUSTOMER_USER)
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
@AccessFor(init = Role.CONTRACTUAL_CONTACT, update = Role.CONTRACTUAL_CONTACT, read = ACTUAL_CUSTOMER_USER)
|
||||||
|
@ParentId(resolver = GivenService.class)
|
||||||
|
Long parentId;
|
||||||
|
|
||||||
|
@AccessFor(init = {TECHNICAL_CONTACT, FINANCIAL_CONTACT}, update = {TECHNICAL_CONTACT, FINANCIAL_CONTACT})
|
||||||
|
String restrictedField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GivenDtoWithMultipleSelfId {
|
||||||
|
|
||||||
|
@SelfId(resolver = GivenChildService.class)
|
||||||
|
@AccessFor(read = Role.ANY_CUSTOMER_USER)
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
@SelfId(resolver = GivenChildService.class)
|
||||||
|
@AccessFor(read = Role.ANY_CUSTOMER_USER)
|
||||||
|
Long id2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class GivenDtoWithUnknownFieldType {
|
||||||
|
|
||||||
|
@SelfId(resolver = GivenChildService.class)
|
||||||
|
@AccessFor(read = Role.ANYBODY)
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
@AccessFor(init = Role.ANYBODY, read = Role.ANYBODY)
|
||||||
|
Arbitrary unknown;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Arbitrary {
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,8 @@ package org.hostsharing.hsadminng.service.accessfilter;
|
|||||||
|
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class JSonBuilder {
|
public class JSonBuilder {
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
@ -12,6 +14,15 @@ public class JSonBuilder {
|
|||||||
json.append(": ");
|
json.append(": ");
|
||||||
if (prop.right instanceof Number) {
|
if (prop.right instanceof Number) {
|
||||||
json.append(prop.right);
|
json.append(prop.right);
|
||||||
|
} else if (prop.right instanceof List) {
|
||||||
|
json.append("[");
|
||||||
|
for ( int n = 0; n < ((List<Integer>)prop.right).size(); ++n ) {
|
||||||
|
if ( n > 0 ) {
|
||||||
|
json.append(",");
|
||||||
|
}
|
||||||
|
json.append(((List<Integer>)prop.right).get(n));
|
||||||
|
}
|
||||||
|
json.append("]");
|
||||||
} else {
|
} else {
|
||||||
json.append(inQuotes(prop.right));
|
json.append(inQuotes(prop.right));
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,10 @@ package org.hostsharing.hsadminng.service.accessfilter;
|
|||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.fasterxml.jackson.core.ObjectCodec;
|
import com.fasterxml.jackson.core.ObjectCodec;
|
||||||
import com.fasterxml.jackson.core.TreeNode;
|
import com.fasterxml.jackson.core.TreeNode;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||||
import org.hostsharing.hsadminng.service.IdToDtoResolver;
|
|
||||||
import org.hostsharing.hsadminng.service.dto.FluentBuilder;
|
|
||||||
import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException;
|
import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
@ -18,10 +18,14 @@ import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
|||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
|
import static org.hostsharing.hsadminng.service.accessfilter.JSonAccessFilterTestFixture.*;
|
||||||
import static org.hostsharing.hsadminng.service.accessfilter.JSonBuilder.asJSon;
|
import static org.hostsharing.hsadminng.service.accessfilter.JSonBuilder.asJSon;
|
||||||
import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenAuthenticatedUser;
|
import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenAuthenticatedUser;
|
||||||
import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenUserHavingRole;
|
import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenUserHavingRole;
|
||||||
@ -54,6 +58,9 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private GivenChildService givenChildService;
|
private GivenChildService givenChildService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private GivenCustomerService givenCustomerService;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() {
|
public void init() {
|
||||||
givenAuthenticatedUser();
|
givenAuthenticatedUser();
|
||||||
@ -63,11 +70,21 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
given(autowireCapableBeanFactory.createBean(GivenService.class)).willReturn(givenService);
|
given(autowireCapableBeanFactory.createBean(GivenService.class)).willReturn(givenService);
|
||||||
given(givenService.findOne(1234L)).willReturn(Optional.of(new GivenDto()
|
given(givenService.findOne(1234L)).willReturn(Optional.of(new GivenDto()
|
||||||
.with(dto -> dto.id = 1234L)
|
.with(dto -> dto.id = 1234L)
|
||||||
.with(dto -> dto.openIntegerField = 1)
|
.with(dto -> dto.customerId = 888L)
|
||||||
.with(dto -> dto.openLongField = 2L)
|
.with(dto -> dto.openIntegerField = 11111)
|
||||||
.with(dto -> dto.openStringField = "3")
|
.with(dto -> dto.openPrimitiveIntField = 2222)
|
||||||
|
.with(dto -> dto.openLongField = 33333333333333L)
|
||||||
|
.with(dto -> dto.openPrimitiveLongField = 44444444L)
|
||||||
|
.with(dto -> dto.openBooleanField = true)
|
||||||
|
.with(dto -> dto.openPrimitiveBooleanField = false)
|
||||||
|
.with(dto -> dto.openBigDecimalField = new BigDecimal("9876543.09"))
|
||||||
|
.with(dto -> dto.openStringField = "3333")
|
||||||
.with(dto -> dto.restrictedField = "initial value of restricted field")
|
.with(dto -> dto.restrictedField = "initial value of restricted field")
|
||||||
));
|
));
|
||||||
|
given(autowireCapableBeanFactory.createBean(GivenCustomerService.class)).willReturn(givenCustomerService);
|
||||||
|
given(givenCustomerService.findOne(888L)).willReturn(Optional.of(new GivenCustomerDto()
|
||||||
|
.with(dto -> dto.id = 888L)
|
||||||
|
));
|
||||||
|
|
||||||
given(jsonParser.getCodec()).willReturn(codec);
|
given(jsonParser.getCodec()).willReturn(codec);
|
||||||
}
|
}
|
||||||
@ -77,6 +94,7 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
// given
|
// given
|
||||||
givenJSonTree(asJSon(
|
givenJSonTree(asJSon(
|
||||||
ImmutablePair.of("id", 1234L),
|
ImmutablePair.of("id", 1234L),
|
||||||
|
ImmutablePair.of("customerId", 888L),
|
||||||
ImmutablePair.of("openStringField", null)));
|
ImmutablePair.of("openStringField", null)));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@ -91,6 +109,7 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
// given
|
// given
|
||||||
givenJSonTree(asJSon(
|
givenJSonTree(asJSon(
|
||||||
ImmutablePair.of("id", 1234L),
|
ImmutablePair.of("id", 1234L),
|
||||||
|
ImmutablePair.of("customerId", 888L),
|
||||||
ImmutablePair.of("openStringField", "String Value")));
|
ImmutablePair.of("openStringField", "String Value")));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@ -105,6 +124,7 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
// given
|
// given
|
||||||
givenJSonTree(asJSon(
|
givenJSonTree(asJSon(
|
||||||
ImmutablePair.of("id", 1234L),
|
ImmutablePair.of("id", 1234L),
|
||||||
|
ImmutablePair.of("customerId", 888L),
|
||||||
ImmutablePair.of("openIntegerField", 1234)));
|
ImmutablePair.of("openIntegerField", 1234)));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@ -115,26 +135,68 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldDeserializeLongField() throws IOException {
|
// TODO: split in separate tests for each type, you see all errors at once (if any) and it's easier to debug when there are problems
|
||||||
|
public void shouldDeserializeAcessibleFieldOfAnyType() throws IOException {
|
||||||
// given
|
// given
|
||||||
givenJSonTree(asJSon(
|
givenJSonTree(asJSon(
|
||||||
ImmutablePair.of("id", 1234L),
|
ImmutablePair.of("id", 1234L),
|
||||||
ImmutablePair.of("openLongField", 1234L)));
|
ImmutablePair.of("customerId", 888L),
|
||||||
|
ImmutablePair.of("openIntegerField", 11),
|
||||||
|
ImmutablePair.of("openPrimitiveIntField", 22),
|
||||||
|
ImmutablePair.of("openLongField", 333333333333333333L),
|
||||||
|
ImmutablePair.of("openPrimitiveLongField", 44444L),
|
||||||
|
ImmutablePair.of("openBooleanField", true),
|
||||||
|
ImmutablePair.of("openPrimitiveBooleanField", false),
|
||||||
|
// TODO: ImmutablePair.of("openBigDecimalField", new BigDecimal("99999999999999999999.1")),
|
||||||
|
// check why DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS is not working!
|
||||||
|
ImmutablePair.of("openBigDecimalField", new BigDecimal("99999999999999.1")),
|
||||||
|
ImmutablePair.of("openLocalDateField", LocalDate.parse("2019-04-25")),
|
||||||
|
ImmutablePair.of("openLocalDateField2", Arrays.asList(2019, 4, 24)),
|
||||||
|
ImmutablePair.of("openEnumField", TestEnum.GREEN)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDto.class).deserialize();
|
GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDto.class).deserialize();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(actualDto.openLongField).isEqualTo(1234L);
|
assertThat(actualDto.openIntegerField).isEqualTo(11);
|
||||||
|
assertThat(actualDto.openPrimitiveIntField).isEqualTo(22);
|
||||||
|
assertThat(actualDto.openLongField).isEqualTo(333333333333333333L);
|
||||||
|
assertThat(actualDto.openPrimitiveLongField).isEqualTo(44444L);
|
||||||
|
assertThat(actualDto.openBooleanField).isEqualTo(true);
|
||||||
|
assertThat(actualDto.openPrimitiveBooleanField).isEqualTo(false);
|
||||||
|
assertThat(actualDto.openBigDecimalField).isEqualTo(new BigDecimal("99999999999999.1"));
|
||||||
|
assertThat(actualDto.openLocalDateField).isEqualTo(LocalDate.parse("2019-04-25"));
|
||||||
|
assertThat(actualDto.openLocalDateField2).isEqualTo(LocalDate.parse("2019-04-24"));
|
||||||
|
assertThat(actualDto.openEnumField).isEqualTo(TestEnum.GREEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotDeserializeFieldWithUnknownJSonNodeType() throws IOException {
|
||||||
|
// given
|
||||||
|
givenJSonTree(asJSon(
|
||||||
|
ImmutablePair.of("id", 1234L),
|
||||||
|
ImmutablePair.of("customerId", 888L),
|
||||||
|
ImmutablePair.of("openArrayField", Arrays.asList(11, 22, 33))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// when
|
||||||
|
Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDto.class).deserialize());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(exception).isInstanceOf(NotImplementedException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldDeserializeStringFieldIfRequiredRoleIsCoveredByUser() throws IOException {
|
public void shouldDeserializeStringFieldIfRequiredRoleIsCoveredByUser() throws IOException {
|
||||||
// given
|
// given
|
||||||
givenAuthenticatedUser();
|
givenAuthenticatedUser();
|
||||||
givenUserHavingRole(GivenDto.class, 1234L, Role.FINANCIAL_CONTACT);
|
givenUserHavingRole(GivenCustomerDto.class, 888L, Role.FINANCIAL_CONTACT);
|
||||||
givenJSonTree(asJSon(
|
givenJSonTree(asJSon(
|
||||||
ImmutablePair.of("id", 1234L),
|
ImmutablePair.of("id", 1234L),
|
||||||
|
ImmutablePair.of("customerId", 888L),
|
||||||
ImmutablePair.of("restrictedField", "update value of restricted field")));
|
ImmutablePair.of("restrictedField", "update value of restricted field")));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@ -148,9 +210,10 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
public void shouldDeserializeUnchangedStringFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
public void shouldDeserializeUnchangedStringFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
||||||
// given
|
// given
|
||||||
givenAuthenticatedUser();
|
givenAuthenticatedUser();
|
||||||
givenUserHavingRole(GivenDto.class, 1234L, Role.ANY_CUSTOMER_USER);
|
givenUserHavingRole(GivenCustomerDto.class, 888L, Role.ACTUAL_CUSTOMER_USER);
|
||||||
givenJSonTree(asJSon(
|
givenJSonTree(asJSon(
|
||||||
ImmutablePair.of("id", 1234L),
|
ImmutablePair.of("id", 1234L),
|
||||||
|
ImmutablePair.of("customerId", 888L),
|
||||||
ImmutablePair.of("restrictedField", "initial value of restricted field")));
|
ImmutablePair.of("restrictedField", "initial value of restricted field")));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@ -164,8 +227,11 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
public void shouldNotDeserializeUpatedStringFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
public void shouldNotDeserializeUpatedStringFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
||||||
// given
|
// given
|
||||||
givenAuthenticatedUser();
|
givenAuthenticatedUser();
|
||||||
givenUserHavingRole(GivenDto.class, 1L, Role.ANY_CUSTOMER_USER);
|
givenUserHavingRole(GivenCustomerDto.class, 888L, Role.ACTUAL_CUSTOMER_USER);
|
||||||
givenJSonTree(asJSon(ImmutablePair.of("restrictedField", "updated value of restricted field")));
|
givenJSonTree(asJSon(
|
||||||
|
ImmutablePair.of("customerId", 888L),
|
||||||
|
ImmutablePair.of("restrictedField", "updated value of restricted field"))
|
||||||
|
);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDto.class).deserialize());
|
Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDto.class).deserialize());
|
||||||
@ -181,8 +247,11 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
public void shouldInitializeFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
public void shouldInitializeFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
||||||
// given
|
// given
|
||||||
givenAuthenticatedUser();
|
givenAuthenticatedUser();
|
||||||
givenUserHavingRole(GivenDto.class, 1L, Role.ANY_CUSTOMER_USER);
|
givenUserHavingRole(GivenCustomerDto.class, 888L, Role.ACTUAL_CUSTOMER_USER);
|
||||||
givenJSonTree(asJSon(ImmutablePair.of("restrictedField", "another value of restricted field")));
|
givenJSonTree(asJSon(
|
||||||
|
ImmutablePair.of("customerId", 888L),
|
||||||
|
ImmutablePair.of("restrictedField", "another value of restricted field"))
|
||||||
|
);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDto.class).deserialize());
|
Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDto.class).deserialize());
|
||||||
@ -199,7 +268,9 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
// given
|
// given
|
||||||
givenAuthenticatedUser();
|
givenAuthenticatedUser();
|
||||||
givenUserHavingRole(GivenDto.class, 9999L, Role.CONTRACTUAL_CONTACT);
|
givenUserHavingRole(GivenDto.class, 9999L, Role.CONTRACTUAL_CONTACT);
|
||||||
givenJSonTree(asJSon(ImmutablePair.of("parentId", 1234L)));
|
givenJSonTree(asJSon(
|
||||||
|
ImmutablePair.of("parentId", 1234L))
|
||||||
|
);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenChildDto.class).deserialize());
|
Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenChildDto.class).deserialize());
|
||||||
@ -216,7 +287,9 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
// given
|
// given
|
||||||
givenAuthenticatedUser();
|
givenAuthenticatedUser();
|
||||||
givenUserHavingRole(GivenDto.class, 1234L, Role.CONTRACTUAL_CONTACT);
|
givenUserHavingRole(GivenDto.class, 1234L, Role.CONTRACTUAL_CONTACT);
|
||||||
givenJSonTree(asJSon(ImmutablePair.of("parentId", 1234L)));
|
givenJSonTree(asJSon(
|
||||||
|
ImmutablePair.of("parentId", 1234L))
|
||||||
|
);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final GivenChildDto actualDto = new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenChildDto.class).deserialize();
|
final GivenChildDto actualDto = new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenChildDto.class).deserialize();
|
||||||
@ -229,9 +302,10 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
public void shouldNotUpdateFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
public void shouldNotUpdateFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
||||||
// given
|
// given
|
||||||
givenAuthenticatedUser();
|
givenAuthenticatedUser();
|
||||||
givenUserHavingRole(GivenDto.class, 1234L, Role.ANY_CUSTOMER_USER);
|
givenUserHavingRole(GivenCustomerDto.class, 888L, Role.ACTUAL_CUSTOMER_USER);
|
||||||
givenJSonTree(asJSon(
|
givenJSonTree(asJSon(
|
||||||
ImmutablePair.of("id", 1234L),
|
ImmutablePair.of("id", 1234L),
|
||||||
|
ImmutablePair.of("customerId", 888L),
|
||||||
ImmutablePair.of("restrictedField", "Restricted String Value")));
|
ImmutablePair.of("restrictedField", "Restricted String Value")));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@ -256,60 +330,29 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
assertThat(exception).isInstanceOf(AssertionError.class).hasMessage("multiple @SelfId detected in GivenDtoWithMultipleSelfId");
|
assertThat(exception).isInstanceOf(AssertionError.class).hasMessage("multiple @SelfId detected in GivenDtoWithMultipleSelfId");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldDetectUnknownFieldType() throws IOException {
|
||||||
|
// given
|
||||||
|
givenAuthenticatedUser();
|
||||||
|
givenUserHavingRole(Role.ADMIN);
|
||||||
|
givenJSonTree(asJSon(ImmutablePair.of("unknown", new Arbitrary())));
|
||||||
|
|
||||||
|
// when
|
||||||
|
Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDtoWithUnknownFieldType.class).deserialize());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(exception).isInstanceOf(NotImplementedException.class)
|
||||||
|
.hasMessageStartingWith("property type not yet implemented: ")
|
||||||
|
.hasMessageContaining("Arbitrary")
|
||||||
|
.hasMessageContaining("GivenDtoWithUnknownFieldType.unknown");
|
||||||
|
}
|
||||||
|
|
||||||
// --- only fixture code below ---
|
// --- only fixture code below ---
|
||||||
|
|
||||||
private void givenJSonTree(String givenJSon) throws IOException {
|
private void givenJSonTree(String givenJSon) throws IOException {
|
||||||
|
final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
objectMapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
|
||||||
given(codec.readTree(jsonParser)).willReturn(new ObjectMapper().readTree(givenJSon));
|
given(codec.readTree(jsonParser)).willReturn(new ObjectMapper().readTree(givenJSon));
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class GivenService implements IdToDtoResolver<GivenDto> {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class GivenDto extends FluentBuilder<GivenDto> {
|
|
||||||
|
|
||||||
@SelfId(resolver = GivenService.class)
|
|
||||||
@AccessFor(read = Role.ANY_CUSTOMER_USER)
|
|
||||||
Long id;
|
|
||||||
|
|
||||||
@AccessFor(init = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT}, update = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT})
|
|
||||||
String restrictedField;
|
|
||||||
|
|
||||||
@AccessFor(init = Role.ANYBODY, update = Role.ANYBODY)
|
|
||||||
String openStringField;
|
|
||||||
|
|
||||||
@AccessFor(init = Role.ANYBODY, update = Role.ANYBODY)
|
|
||||||
Integer openIntegerField;
|
|
||||||
|
|
||||||
@AccessFor(init = Role.ANYBODY, update = Role.ANYBODY)
|
|
||||||
Long openLongField;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class GivenChildService implements IdToDtoResolver<GivenChildDto> {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class GivenChildDto extends FluentBuilder<GivenChildDto> {
|
|
||||||
|
|
||||||
@SelfId(resolver = GivenChildService.class)
|
|
||||||
@AccessFor(read = Role.ANY_CUSTOMER_USER)
|
|
||||||
Long id;
|
|
||||||
|
|
||||||
@AccessFor(init = Role.CONTRACTUAL_CONTACT, update = Role.CONTRACTUAL_CONTACT, read = Role.ACTUAL_CUSTOMER_USER)
|
|
||||||
@ParentId(resolver = GivenService.class)
|
|
||||||
Long parentId;
|
|
||||||
|
|
||||||
@AccessFor(init = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT}, update = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT})
|
|
||||||
String restrictedField;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class GivenDtoWithMultipleSelfId {
|
|
||||||
|
|
||||||
@SelfId(resolver = GivenChildService.class)
|
|
||||||
@AccessFor(read = Role.ANY_CUSTOMER_USER)
|
|
||||||
Long id;
|
|
||||||
|
|
||||||
@SelfId(resolver = GivenChildService.class)
|
|
||||||
@AccessFor(read = Role.ANY_CUSTOMER_USER)
|
|
||||||
Long id2;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,6 @@ package org.hostsharing.hsadminng.service.accessfilter;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
|
||||||
import org.apache.commons.lang3.RandomUtils;
|
|
||||||
import org.hostsharing.hsadminng.service.IdToDtoResolver;
|
|
||||||
import org.hostsharing.hsadminng.service.dto.FluentBuilder;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -20,6 +16,7 @@ import java.util.Optional;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
|
import static org.hostsharing.hsadminng.service.accessfilter.JSonAccessFilterTestFixture.*;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@ -64,6 +61,87 @@ public class JSonSerializerWithAccessFilterUnitTest {
|
|||||||
verify(jsonGenerator).writeStringField("openStringField", givenDTO.openStringField);
|
verify(jsonGenerator).writeStringField("openStringField", givenDTO.openStringField);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSerializeIntegerField() throws IOException {
|
||||||
|
// when
|
||||||
|
new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize();
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(jsonGenerator).writeNumberField("openIntegerField", givenDTO.openIntegerField);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSerializePrimitiveIntField() throws IOException {
|
||||||
|
// when
|
||||||
|
new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize();
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(jsonGenerator).writeNumberField("openPrimitiveIntField", givenDTO.openPrimitiveIntField);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSerializeLongField() throws IOException {
|
||||||
|
// when
|
||||||
|
new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize();
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(jsonGenerator).writeNumberField("openLongField", givenDTO.openLongField);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSerializePrimitiveLongField() throws IOException {
|
||||||
|
// when
|
||||||
|
new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize();
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(jsonGenerator).writeNumberField("openPrimitiveLongField", givenDTO.openPrimitiveLongField);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSerializeBooleanField() throws IOException {
|
||||||
|
// when
|
||||||
|
new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize();
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(jsonGenerator).writeBooleanField("openBooleanField", givenDTO.openBooleanField);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSerializePrimitiveBooleanField() throws IOException {
|
||||||
|
// when
|
||||||
|
new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize();
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(jsonGenerator).writeBooleanField("openPrimitiveBooleanField", givenDTO.openPrimitiveBooleanField);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSerializeBigDecimalField() throws IOException {
|
||||||
|
// when
|
||||||
|
new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize();
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(jsonGenerator).writeNumberField("openBigDecimalField", givenDTO.openBigDecimalField);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSerializeLocalDateField() throws IOException {
|
||||||
|
// when
|
||||||
|
new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize();
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(jsonGenerator).writeStringField("openLocalDateField", givenDTO.openLocalDateFieldAsString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSerializeEnumField() throws IOException {
|
||||||
|
// when
|
||||||
|
new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize();
|
||||||
|
|
||||||
|
// then
|
||||||
|
verify(jsonGenerator).writeStringField("openEnumField", givenDTO.openEnumFieldAsString);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldSerializeRestrictedFieldIfRequiredRoleIsCoveredByUser() throws IOException {
|
public void shouldSerializeRestrictedFieldIfRequiredRoleIsCoveredByUser() throws IOException {
|
||||||
|
|
||||||
@ -113,50 +191,4 @@ public class JSonSerializerWithAccessFilterUnitTest {
|
|||||||
|
|
||||||
// --- fixture code below ---
|
// --- fixture code below ---
|
||||||
|
|
||||||
private GivenDto createSampleDto() {
|
|
||||||
final GivenDto dto = new GivenDto();
|
|
||||||
dto.customerId = 888L;
|
|
||||||
dto.restrictedField = RandomStringUtils.randomAlphabetic(10);
|
|
||||||
dto.openStringField = RandomStringUtils.randomAlphabetic(10);
|
|
||||||
dto.openIntegerField = RandomUtils.nextInt();
|
|
||||||
dto.openLongField = RandomUtils.nextLong();
|
|
||||||
return dto;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class GivenCustomerDto extends FluentBuilder<GivenCustomerDto> {
|
|
||||||
@SelfId(resolver = GivenService.class)
|
|
||||||
@AccessFor(read = Role.ANYBODY)
|
|
||||||
Long id;
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract class GivenCustomerService implements IdToDtoResolver<GivenCustomerDto> {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class GivenDto {
|
|
||||||
|
|
||||||
@SelfId(resolver = GivenService.class)
|
|
||||||
@AccessFor(read = Role.ANYBODY)
|
|
||||||
Long id;
|
|
||||||
|
|
||||||
@ParentId(resolver = GivenCustomerService.class)
|
|
||||||
@AccessFor(read = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT})
|
|
||||||
Long customerId;
|
|
||||||
|
|
||||||
@AccessFor(read = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT})
|
|
||||||
String restrictedField;
|
|
||||||
|
|
||||||
@AccessFor(read = Role.ANYBODY)
|
|
||||||
String openStringField;
|
|
||||||
|
|
||||||
@AccessFor(read = Role.ANYBODY)
|
|
||||||
Integer openIntegerField;
|
|
||||||
|
|
||||||
@AccessFor(read = Role.ANYBODY)
|
|
||||||
Long openLongField;
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract class GivenService implements IdToDtoResolver<GivenService> {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package org.hostsharing.hsadminng.service.accessfilter;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
public class RoleUnitTest {
|
public class RoleUnitTest {
|
||||||
@ -69,15 +71,60 @@ public class RoleUnitTest {
|
|||||||
assertThat(Role.FINANCIAL_CONTACT.covers(Role.ACTUAL_CUSTOMER_USER)).isFalse();
|
assertThat(Role.FINANCIAL_CONTACT.covers(Role.ACTUAL_CUSTOMER_USER)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isIndependent() {
|
||||||
|
assertThat(Role.HOSTMASTER.isIndependent()).isTrue();
|
||||||
|
assertThat(Role.SUPPORTER.isIndependent()).isTrue();
|
||||||
|
|
||||||
|
assertThat(Role.CONTRACTUAL_CONTACT.isIndependent()).isFalse();
|
||||||
|
assertThat(Role.ANY_CUSTOMER_USER.isIndependent()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isBroadest() {
|
||||||
|
assertThat(Role.broadest(Role.HOSTMASTER, Role.CONTRACTUAL_CONTACT)).isEqualTo(Role.HOSTMASTER);
|
||||||
|
assertThat(Role.broadest(Role.CONTRACTUAL_CONTACT, Role.HOSTMASTER)).isEqualTo(Role.HOSTMASTER);
|
||||||
|
assertThat(Role.broadest(Role.CONTRACTUAL_CONTACT, Role.ANY_CUSTOMER_USER)).isEqualTo(Role.CONTRACTUAL_CONTACT);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void isAllowedToInit() {
|
public void isAllowedToInit() {
|
||||||
|
assertThat(Role.HOSTMASTER.isAllowedToInit(someFieldWithoutAccessForAnnotation)).isFalse();
|
||||||
|
assertThat(Role.SUPPORTER.isAllowedToInit(someFieldWithoutAccessForAnnotation)).isFalse();
|
||||||
|
assertThat(Role.ADMIN.isAllowedToInit(someFieldWithAccessForAnnotation)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void isAllowedToUpdate() {
|
public void isAllowedToUpdate() {
|
||||||
|
assertThat(Role.HOSTMASTER.isAllowedToUpdate(someFieldWithoutAccessForAnnotation)).isFalse();
|
||||||
|
assertThat(Role.ANY_CUSTOMER_CONTACT.isAllowedToUpdate(someFieldWithAccessForAnnotation)).isFalse();
|
||||||
|
assertThat(Role.SUPPORTER.isAllowedToUpdate(someFieldWithAccessForAnnotation)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void isAllowedToRead() {
|
public void isAllowedToRead() {
|
||||||
|
assertThat(Role.HOSTMASTER.isAllowedToRead(someFieldWithoutAccessForAnnotation)).isFalse();
|
||||||
|
assertThat(Role.ANY_CUSTOMER_USER.isAllowedToRead(someFieldWithAccessForAnnotation)).isFalse();
|
||||||
|
assertThat(Role.ANY_CUSTOMER_CONTACT.isAllowedToRead(someFieldWithAccessForAnnotation)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- only test fixture below ---
|
||||||
|
|
||||||
|
static class TestDto {
|
||||||
|
@AccessFor(init = Role.ADMIN, update = Role.SUPPORTER, read = Role.ANY_CUSTOMER_CONTACT)
|
||||||
|
private Integer someFieldWithAccessForAnnotation;
|
||||||
|
|
||||||
|
private Integer someFieldWithoutAccessForAnnotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Field someFieldWithoutAccessForAnnotation;
|
||||||
|
private static Field someFieldWithAccessForAnnotation;
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
someFieldWithoutAccessForAnnotation = TestDto.class.getDeclaredField("someFieldWithoutAccessForAnnotation");
|
||||||
|
someFieldWithAccessForAnnotation = TestDto.class.getDeclaredField("someFieldWithAccessForAnnotation");
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
throw new AssertionError("precondition failed", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,7 +236,29 @@ public class CustomerResourceIntTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Transactional
|
@Transactional
|
||||||
public void createCustomerWithExistingId() throws Exception {
|
public void createCustomerWithExistingIdIsRejected() throws Exception {
|
||||||
|
// Initialize the database
|
||||||
|
final long existingCustomerId = customerRepository.saveAndFlush(customer).getId();
|
||||||
|
int databaseSizeBeforeCreate = customerRepository.findAll().size();
|
||||||
|
|
||||||
|
// Create the Customer with an existing ID
|
||||||
|
customer.setId(existingCustomerId);
|
||||||
|
CustomerDTO customerDTO = customerMapper.toDto(customer);
|
||||||
|
|
||||||
|
// An entity with an existing ID cannot be created, so this API call must fail
|
||||||
|
restCustomerMockMvc.perform(post("/api/customers")
|
||||||
|
.contentType(TestUtil.APPLICATION_JSON_UTF8)
|
||||||
|
.content(TestUtil.convertObjectToJsonBytes(customerDTO)))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
|
||||||
|
// Validate the Customer in the database
|
||||||
|
List<Customer> customerList = customerRepository.findAll();
|
||||||
|
assertThat(customerList).hasSize(databaseSizeBeforeCreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Transactional
|
||||||
|
public void createCustomerWithNonExistingIdIsRejected() throws Exception {
|
||||||
int databaseSizeBeforeCreate = customerRepository.findAll().size();
|
int databaseSizeBeforeCreate = customerRepository.findAll().size();
|
||||||
|
|
||||||
// Create the Customer with an existing ID
|
// Create the Customer with an existing ID
|
||||||
|
Loading…
Reference in New Issue
Block a user