From 998a5a8aa1f6a4f47bea95b72f384a93bb57f13c Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 19 Apr 2019 12:08:30 +0200 Subject: [PATCH] WIP JSonDeserializerWithAccessFilter --- .../JSonDeserializerWithAccessFilter.java | 34 +++++ .../hsadminng/service/dto/CustomerDTO.java | 29 +--- .../service/util/ReflectionUtil.java | 19 +++ ...nDeserializerWithAccessFilterUnitTest.java | 125 ++++++++++++++++++ 4 files changed, 182 insertions(+), 25 deletions(-) create mode 100644 src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java create mode 100644 src/main/java/org/hostsharing/hsadminng/service/util/ReflectionUtil.java create mode 100644 src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java new file mode 100644 index 00000000..3ab6f061 --- /dev/null +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java @@ -0,0 +1,34 @@ +package org.hostsharing.hsadminng.service.accessfilter; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.DeserializationContext; + +import static org.hostsharing.hsadminng.service.util.ReflectionUtil.unchecked; + +public class JSonDeserializerWithAccessFilter { + + private final T dto; + private final TreeNode treeNode; + + public JSonDeserializerWithAccessFilter(final JsonParser jsonParser, final DeserializationContext deserializationContext, Class dtoClass) { + this.treeNode = unchecked(() -> jsonParser.getCodec().readTree(jsonParser)); + this.dto = unchecked(() -> dtoClass.newInstance()); + } + + public T deserialize() { +// +// CustomerDTO dto = new CustomerDTO(); +// dto.setId(((IntNode) treeNode.get("id")).asLong()); +// dto.setReference(((IntNode) treeNode.get("reference")).asInt()); +// dto.setPrefix(((TextNode) treeNode.get("prefix")).asText()); +// dto.setName(((TextNode) treeNode.get("name")).asText()); +// dto.setContractualAddress(((TextNode) treeNode.get("contractualAddress")).asText()); +// dto.setContractualSalutation(((TextNode) treeNode.get("contractualSalutation")).asText()); +// dto.setBillingAddress(((TextNode) treeNode.get("billingAddress")).asText()); +// dto.setBillingSalutation(((TextNode) treeNode.get("billingSalutation")).asText()); +// dto.setRemark(((TextNode) treeNode.get("remark")).asText()); + + return dto; + } +} diff --git a/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java b/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java index 18b6e793..58abb893 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java @@ -1,27 +1,19 @@ package org.hostsharing.hsadminng.service.dto; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.node.IntNode; import com.fasterxml.jackson.databind.node.TextNode; -import org.apache.commons.lang3.NotImplementedException; -import org.hostsharing.hsadminng.security.SecurityUtils; import org.hostsharing.hsadminng.service.accessfilter.AccessFor; +import org.hostsharing.hsadminng.service.accessfilter.JSonDeserializerWithAccessFilter; import org.hostsharing.hsadminng.service.accessfilter.Role; import org.springframework.boot.jackson.JsonComponent; import javax.validation.constraints.*; import java.io.IOException; import java.io.Serializable; -import java.lang.annotation.*; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Objects; /** @@ -182,23 +174,10 @@ public class CustomerDTO implements Serializable { public static class UserJsonDeserializer extends JsonDeserializer { @Override - public CustomerDTO deserialize(JsonParser jsonParser, - DeserializationContext deserializationContext) throws IOException { + public CustomerDTO deserialize(final JsonParser jsonParser, + final DeserializationContext deserializationContext) throws IOException { - TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser); - - CustomerDTO dto = new CustomerDTO(); - dto.setId(((IntNode) treeNode.get("id")).asLong()); - dto.setReference(((IntNode) treeNode.get("reference")).asInt()); - dto.setPrefix(((TextNode) treeNode.get("prefix")).asText()); - dto.setName(((TextNode) treeNode.get("name")).asText()); - dto.setContractualAddress(((TextNode) treeNode.get("contractualAddress")).asText()); - dto.setContractualSalutation(((TextNode) treeNode.get("contractualSalutation")).asText()); - dto.setBillingAddress(((TextNode) treeNode.get("billingAddress")).asText()); - dto.setBillingSalutation(((TextNode) treeNode.get("billingSalutation")).asText()); - dto.setRemark(((TextNode) treeNode.get("remark")).asText()); - - return dto; + return new JSonDeserializerWithAccessFilter(jsonParser, deserializationContext, CustomerDTO.class).deserialize(); } } } diff --git a/src/main/java/org/hostsharing/hsadminng/service/util/ReflectionUtil.java b/src/main/java/org/hostsharing/hsadminng/service/util/ReflectionUtil.java new file mode 100644 index 00000000..ff5c8de3 --- /dev/null +++ b/src/main/java/org/hostsharing/hsadminng/service/util/ReflectionUtil.java @@ -0,0 +1,19 @@ +package org.hostsharing.hsadminng.service.util; + +import java.util.function.Supplier; + +public class ReflectionUtil { + + @FunctionalInterface + public interface ThrowingSupplier { + T get() throws Exception; + } + + public static T unchecked(final ThrowingSupplier supplier) { + try { + return supplier.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java new file mode 100644 index 00000000..c0c93a47 --- /dev/null +++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java @@ -0,0 +1,125 @@ +package org.hostsharing.hsadminng.service.accessfilter; + +import com.fasterxml.jackson.core.JsonGenerator; +import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenLoginUserWithRole; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +public class JSonDeserializerWithAccessFilterUnitTest { + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + public JsonGenerator jsonGenerator; + + @Before + public void init() { + givenLoginUserWithRole(Role.ANY_CUSTOMER_USER); + } + + @Test + public void shouldDeserializeStringField() throws IOException { + // given + final String givenJSon = asJSon(ImmutablePair.of("stringField", "String Value")); + + // when + new JSonDeserializerWithAccessFilter().deserialize(givenJSon, jsonGenerator, null); + + // then + verify(jsonGenerator).writeStringField("openStringField", givenDTO.openStringField); + } + + @Test + public void shouldSerializeRestrictedFieldIfRequiredRoleIsCoveredByUser() throws IOException { + + // given + givenLoginUserWithRole(Role.FINANCIAL_CONTACT); + + // when + new JSonSerializerWithAccessFilter().serialize(givenDTO, jsonGenerator, null); + + // then + verify(jsonGenerator).writeStringField("restrictedField", givenDTO.restrictedField); + } + + @Test + public void shouldNotSerializeRestrictedFieldIfRequiredRoleIsNotCoveredByUser() throws IOException { + + // given + givenLoginUserWithRole(Role.ANY_CUSTOMER_USER); + + // when + new JSonSerializerWithAccessFilter().serialize(givenDTO, jsonGenerator, null); + + // then + verify(jsonGenerator, never()).writeStringField("restrictedField", givenDTO.restrictedField); + } + + @Test + public void shouldThrowExceptionForUnimplementedFieldType() throws IOException { + + // given + class Arbitrary { + } + class GivenDtoWithUnimplementedFieldType { + @AccessFor(read = Role.ANYBODY) + Arbitrary fieldWithUnimplementedType; + } + final GivenDtoWithUnimplementedFieldType givenDto = new GivenDtoWithUnimplementedFieldType(); + + // when + Throwable actual = catchThrowable(() -> new JSonSerializerWithAccessFilter().serialize(givenDto, jsonGenerator, null)); + + // then + assertThat(actual).isInstanceOf(NotImplementedException.class); + } + + // --- fixture code below --- + + private String asJSon(final ImmutablePair... properties) { + final StringBuilder json = new StringBuilder(); + for ( ImmutablePair prop: properties ) { + json.append(inQuotes(prop.left)); + json.append(": "); + if ( prop.right instanceof Number ) { + json.append(prop.right); + } else { + json.append(inQuotes(prop.right)); + } + json.append(",\n"); + } + return "{\n" + json.substring(0, json.length()-2) + "\n}"; + } + + private String inQuotes(Object value) { + return "\"" + value.toString() + "\""; + } + + private static class GivenDto { + @AccessFor(update = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT}) + String restrictedField; + + @AccessFor(update = Role.ANYBODY) + String openStringField; + + @AccessFor(update = Role.ANYBODY) + Integer openIntegerField; + + @AccessFor(update = Role.ANYBODY) + Long openLongField; + } +}