From 639ea062431ad2e08b83904741612005196eea05 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Wed, 24 Apr 2019 12:30:26 +0200 Subject: [PATCH] JSonAccessFilter with initially working (hardcoded) grand parent role --- .../hsadminng/domain/Customer.java | 14 +- .../hsadminng/domain/Membership.java | 16 +- .../hsadminng/service/CustomerService.java | 3 +- .../hsadminng/service/DtoLoader.java | 7 + .../hsadminng/service/MembershipService.java | 3 +- .../accessfilter/JSonAccessFilter.java | 93 +++++++--- .../JSonDeserializerWithAccessFilter.java | 5 +- .../JSonSerializerWithAccessFilter.java | 11 +- .../hsadminng/service/accessfilter/Role.java | 21 ++- .../hsadminng/service/dto/CustomerDTO.java | 19 +- .../hsadminng/service/dto/MembershipDTO.java | 4 +- .../hsadminng/service/dto/ShareDTO.java | 26 ++- .../service/accessfilter/JSonBuilder.java | 27 +++ ...nDeserializerWithAccessFilterUnitTest.java | 43 ++--- ...SonSerializerWithAccessFilterUnitTest.java | 12 +- .../accessfilter/MockSecurityContext.java | 3 + .../service/dto/MembershipDTOUnitTest.java | 85 +++++++++ .../service/dto/ShareDTOUnitTest.java | 163 ++++++++++++++++++ 18 files changed, 468 insertions(+), 87 deletions(-) create mode 100644 src/main/java/org/hostsharing/hsadminng/service/DtoLoader.java create mode 100644 src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonBuilder.java create mode 100644 src/test/java/org/hostsharing/hsadminng/service/dto/MembershipDTOUnitTest.java create mode 100644 src/test/java/org/hostsharing/hsadminng/service/dto/ShareDTOUnitTest.java diff --git a/src/main/java/org/hostsharing/hsadminng/domain/Customer.java b/src/main/java/org/hostsharing/hsadminng/domain/Customer.java index c7cb1495..8f5aa792 100644 --- a/src/main/java/org/hostsharing/hsadminng/domain/Customer.java +++ b/src/main/java/org/hostsharing/hsadminng/domain/Customer.java @@ -1,15 +1,12 @@ package org.hostsharing.hsadminng.domain; -import com.fasterxml.jackson.annotation.JsonIgnore; - import javax.persistence.*; import javax.validation.constraints.*; - import java.io.Serializable; import java.util.HashSet; -import java.util.Set; import java.util.Objects; +import java.util.Set; /** * A Customer. @@ -65,13 +62,21 @@ public class Customer implements Serializable { @OneToMany(mappedBy = "customer") private Set memberships = new HashSet<>(); + @OneToMany(mappedBy = "customer") private Set sepamandates = new HashSet<>(); + // jhipster-needle-entity-add-field - JHipster will add fields here, do not remove + public Long getId() { return id; } + public Customer id(long id) { + this.id = id; + return this; + } + public void setId(Long id) { this.id = id; } @@ -229,6 +234,7 @@ public class Customer implements Serializable { public void setSepamandates(Set sepaMandates) { this.sepamandates = sepaMandates; } + // jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here, do not remove @Override diff --git a/src/main/java/org/hostsharing/hsadminng/domain/Membership.java b/src/main/java/org/hostsharing/hsadminng/domain/Membership.java index d6e4e5bd..91749ff7 100644 --- a/src/main/java/org/hostsharing/hsadminng/domain/Membership.java +++ b/src/main/java/org/hostsharing/hsadminng/domain/Membership.java @@ -1,17 +1,16 @@ package org.hostsharing.hsadminng.domain; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import javax.persistence.*; -import javax.validation.constraints.*; - +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; import java.time.LocalDate; import java.util.HashSet; -import java.util.Set; import java.util.Objects; +import java.util.Set; /** * A Membership. @@ -46,18 +45,26 @@ public class Membership implements Serializable { @OneToMany(mappedBy = "membership") private Set shares = new HashSet<>(); + @OneToMany(mappedBy = "membership") private Set assets = new HashSet<>(); + @ManyToOne(optional = false) @NotNull @JsonIgnoreProperties("memberships") private Customer customer; // jhipster-needle-entity-add-field - JHipster will add fields here, do not remove + public Long getId() { return id; } + public Membership id(Long id) { + this.id = id; + return this; + } + public void setId(Long id) { this.id = id; } @@ -176,6 +183,7 @@ public class Membership implements Serializable { public void setCustomer(Customer customer) { this.customer = customer; } + // jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here, do not remove @Override diff --git a/src/main/java/org/hostsharing/hsadminng/service/CustomerService.java b/src/main/java/org/hostsharing/hsadminng/service/CustomerService.java index d7153deb..9bff0e66 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/CustomerService.java +++ b/src/main/java/org/hostsharing/hsadminng/service/CustomerService.java @@ -6,7 +6,6 @@ import org.hostsharing.hsadminng.service.dto.CustomerDTO; import org.hostsharing.hsadminng.service.mapper.CustomerMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -19,7 +18,7 @@ import java.util.Optional; */ @Service @Transactional -public class CustomerService { +public class CustomerService implements DtoLoader { private final Logger log = LoggerFactory.getLogger(CustomerService.class); diff --git a/src/main/java/org/hostsharing/hsadminng/service/DtoLoader.java b/src/main/java/org/hostsharing/hsadminng/service/DtoLoader.java new file mode 100644 index 00000000..ae30d79b --- /dev/null +++ b/src/main/java/org/hostsharing/hsadminng/service/DtoLoader.java @@ -0,0 +1,7 @@ +package org.hostsharing.hsadminng.service; + +import java.util.Optional; + +public interface DtoLoader { + Optional findOne(Long id); +} diff --git a/src/main/java/org/hostsharing/hsadminng/service/MembershipService.java b/src/main/java/org/hostsharing/hsadminng/service/MembershipService.java index 12e4fb40..d611e713 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/MembershipService.java +++ b/src/main/java/org/hostsharing/hsadminng/service/MembershipService.java @@ -19,7 +19,7 @@ import java.util.Optional; */ @Service @Transactional -public class MembershipService { +public class MembershipService implements DtoLoader { private final Logger log = LoggerFactory.getLogger(MembershipService.class); @@ -73,6 +73,7 @@ public class MembershipService { * @param id the id of the entity * @return the entity */ + @Override @Transactional(readOnly = true) public Optional findOne(Long id) { log.debug("Request to get Membership : {}", id); diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java index c6faaf04..86d88bba 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java @@ -1,35 +1,33 @@ package org.hostsharing.hsadminng.service.accessfilter; +import org.apache.commons.lang3.NotImplementedException; import org.hostsharing.hsadminng.security.SecurityUtils; +import org.hostsharing.hsadminng.service.CustomerService; +import org.hostsharing.hsadminng.service.DtoLoader; +import org.hostsharing.hsadminng.service.MembershipService; +import org.hostsharing.hsadminng.service.dto.CustomerDTO; +import org.hostsharing.hsadminng.service.dto.MembershipDTO; import org.hostsharing.hsadminng.service.util.ReflectionUtil; +import org.springframework.context.ApplicationContext; +import java.lang.annotation.Annotation; import java.lang.reflect.Field; abstract class JSonAccessFilter { + private final ApplicationContext ctx; final T dto; - Field selfIdField = null; - Field parentIdField = null; + final Field selfIdField; + final Field parentIdField; - JSonAccessFilter(final T dto) { + JSonAccessFilter(final ApplicationContext ctx, final T dto) { + this.ctx = ctx; this.dto = dto; - determineIdFields(); + this.selfIdField = determineFieldWithAnnotation(dto.getClass(), SelfId.class); + this.parentIdField = determineFieldWithAnnotation(dto.getClass(), ParentId.class); } - void determineIdFields() { - for (Field field : dto.getClass().getDeclaredFields()) { - if (field.isAnnotationPresent(SelfId.class)) { - if (selfIdField != null) { - throw new AssertionError("multiple @" + SelfId.class.getSimpleName() + " detected in " + field.getDeclaringClass().getSimpleName()); - } - selfIdField = field; - } - if (field.isAnnotationPresent(ParentId.class)) { - if (parentIdField != null) { - throw new AssertionError("multiple @" + ParentId.class.getSimpleName() + " detected in " + field.getDeclaringClass().getSimpleName()); - } - parentIdField = field; - } - } + boolean isParentIdField(final Field field) { + return field.equals(parentIdField); } Long getId() { @@ -39,27 +37,68 @@ abstract class JSonAccessFilter { return (Long) ReflectionUtil.getValue(dto, selfIdField); } + /** + * @param field to get a display representation for + * @return a simplified, decently user readable, display representation of the given field + */ String toDisplay(final Field field) { return field.getDeclaringClass().getSimpleName() + "." + field.getName(); } + /** + * @return the role of the login user in relation to the dto, this filter is created for. + */ Role getLoginUserRole() { final Role roleOnSelf = getLoginUserRoleOnSelf(); - final Role roleOnParent = getLoginUserRoleOnParent(); - return roleOnSelf.covers(roleOnParent) ? roleOnSelf : roleOnParent; + if ( roleOnSelf.isIndependent() ) { + return roleOnSelf; + } + return getLoginUserRoleOnAncestorOfDtoClassIfHigher(roleOnSelf, dto); } - private Role getLoginUserRoleOnSelf() { - // TODO: find broadest role in self and recursively in parent return SecurityUtils.getLoginUserRoleFor(dto.getClass(), getId() ); } - private Role getLoginUserRoleOnParent() { + private Role getLoginUserRoleOnAncestorOfDtoClassIfHigher(final Role baseRole, final Object dto) { + final Field parentIdField = determineFieldWithAnnotation(dto.getClass(), ParentId.class); + if ( parentIdField == null ) { - return Role.ANYBODY; + return baseRole; } - final ParentId parentId = parentIdField.getAnnotation(ParentId.class); - return SecurityUtils.getLoginUserRoleFor(parentId.value(), (Long) ReflectionUtil.getValue(dto, parentIdField) ); + + final ParentId parentIdAnnot = parentIdField.getAnnotation(ParentId.class); + final Class parentDtoClass = parentIdAnnot.value(); + final Long parentId = (Long) ReflectionUtil.getValue(dto, parentIdField); + final Role roleOnParent = SecurityUtils.getLoginUserRoleFor(parentDtoClass, parentId); + + final Object parentEntity = findParentDto(parentDtoClass, parentId); + return Role.broadest(baseRole, getLoginUserRoleOnAncestorOfDtoClassIfHigher(roleOnParent, parentEntity)); + } + + private Object findParentDto(Class parentDtoClass, Long parentId) { + // TODO: generalize, e.g. via "all beans that implement DtoLoader + if ( parentDtoClass == MembershipDTO.class ) { + final DtoLoader dtoLoader = ctx.getAutowireCapableBeanFactory().createBean(MembershipService.class); + return dtoLoader.findOne(parentId).get(); + } + if ( parentDtoClass == CustomerDTO.class ) { + final DtoLoader dtoLoader = ctx.getAutowireCapableBeanFactory().createBean(CustomerService.class); + return dtoLoader.findOne(parentId).get(); + } + throw new NotImplementedException("no DtoLoader implemented for " + parentDtoClass); + } + + private static Field determineFieldWithAnnotation(final Class dtoClass, final Class idAnnotationClass) { + Field parentIdField = null; + for (Field field : dtoClass.getDeclaredFields()) { + if (field.isAnnotationPresent(idAnnotationClass)) { + if (parentIdField != null) { + throw new AssertionError("multiple @" + idAnnotationClass.getSimpleName() + " detected in " + field.getDeclaringClass().getSimpleName()); + } + parentIdField = field; + } + } + return parentIdField; } } diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java index 9dc104d1..5e918e84 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.node.TextNode; import org.apache.commons.lang3.NotImplementedException; import org.hostsharing.hsadminng.service.util.ReflectionUtil; import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException; +import org.springframework.context.ApplicationContext; import java.lang.reflect.Field; import java.util.HashSet; @@ -21,8 +22,8 @@ public class JSonDeserializerWithAccessFilter extends JSonAccessFilter { private final TreeNode treeNode; private final Set modifiedFields = new HashSet<>(); - public JSonDeserializerWithAccessFilter(final JsonParser jsonParser, final DeserializationContext deserializationContext, Class dtoClass) { - super(unchecked(dtoClass::newInstance)); + public JSonDeserializerWithAccessFilter(final ApplicationContext ctx, final JsonParser jsonParser, final DeserializationContext deserializationContext, Class dtoClass) { + super(ctx, unchecked(dtoClass::newInstance)); this.treeNode = unchecked(() -> jsonParser.getCodec().readTree(jsonParser)); } diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java index b2ec0c57..20d38832 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java @@ -4,18 +4,21 @@ package org.hostsharing.hsadminng.service.accessfilter; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import org.apache.commons.lang3.NotImplementedException; +import org.springframework.context.ApplicationContext; import java.io.IOException; import java.lang.reflect.Field; +import java.time.LocalDate; public class JSonSerializerWithAccessFilter extends JSonAccessFilter { private final JsonGenerator jsonGenerator; private final SerializerProvider serializerProvider; - public JSonSerializerWithAccessFilter(final JsonGenerator jsonGenerator, + public JSonSerializerWithAccessFilter(final ApplicationContext ctx, + final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider, final T dto) { - super(dto); + super(ctx, dto); this.jsonGenerator = jsonGenerator; this.serializerProvider = serializerProvider; } @@ -42,6 +45,10 @@ public class JSonSerializerWithAccessFilter extends JSonAccessFilter { jsonGenerator.writeNumberField(fieldName, (int) get(dto, prop)); } else if (Long.class.isAssignableFrom(prop.getType()) || long.class.isAssignableFrom(prop.getType())) { jsonGenerator.writeNumberField(fieldName, (long) get(dto, prop)); + } else if (LocalDate.class.isAssignableFrom(prop.getType())) { + jsonGenerator.writeStringField(fieldName, get(dto, prop).toString()); // TODO proper format + } else if (Enum.class.isAssignableFrom(prop.getType())) { + jsonGenerator.writeStringField(fieldName, get(dto, prop).toString()); // TODO proper representation } else if (String.class.isAssignableFrom(prop.getType())) { jsonGenerator.writeStringField(fieldName, (String) get(dto, prop)); } else { diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/Role.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/Role.java index 83f667d0..8fbe8c44 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/Role.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/Role.java @@ -87,6 +87,26 @@ public enum Role { this.level = level; } + /** + * @return true if this role is independent of a target object, false otherwise. + */ + public boolean isIndependent() { + return covers(Role.SUPPORTER); + } + + /** + @return the role with the broadest access rights + */ + public static Role broadest(final Role role, final Role... roles) { + Role broadests = role; + for ( Role r: roles ) { + if ( r.covers(broadests)) { + broadests = r; + } + } + return broadests; + } + /** * Determines if the given role is covered by this role. * @@ -163,5 +183,4 @@ public enum Role { } return false; } - } 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 28a84d2a..bae6ca25 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import org.hostsharing.hsadminng.service.accessfilter.*; import org.springframework.boot.jackson.JsonComponent; +import org.springframework.context.ApplicationContext; import javax.validation.constraints.*; import java.io.IOException; @@ -172,24 +173,34 @@ public class CustomerDTO implements Serializable { @JsonComponent public static class CustomerJsonSerializer extends JsonSerializer { + private final ApplicationContext ctx; + + public CustomerJsonSerializer(final ApplicationContext ctx) { + this.ctx = ctx; + } + @Override public void serialize(final CustomerDTO customerDTO, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) throws IOException { - new JSonSerializerWithAccessFilter<>(jsonGenerator, serializerProvider, customerDTO).serialize(); + new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, serializerProvider, customerDTO).serialize(); } } - - @JsonComponent public static class CustomerJsonDeserializer extends JsonDeserializer { + private final ApplicationContext ctx; + + public CustomerJsonDeserializer(final ApplicationContext ctx) { + this.ctx = ctx; + } + @Override public CustomerDTO deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) { - return new JSonDeserializerWithAccessFilter<>(jsonParser, deserializationContext, CustomerDTO.class).deserialize(); + return new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, deserializationContext, CustomerDTO.class).deserialize(); } } } diff --git a/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java b/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java index 15130c3c..c255c3a2 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java @@ -37,10 +37,10 @@ public class MembershipDTO implements Serializable { private String remark; @ParentId(CustomerDTO.class) - @AccessFor(init = Role.ADMIN, read = Role.SUPPORTER) + @AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) private Long customerId; - @AccessFor(read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) + @AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) private String customerPrefix; public MembershipDTO with( diff --git a/src/main/java/org/hostsharing/hsadminng/service/dto/ShareDTO.java b/src/main/java/org/hostsharing/hsadminng/service/dto/ShareDTO.java index e9f873fa..eff5bad4 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/ShareDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/ShareDTO.java @@ -1,35 +1,51 @@ package org.hostsharing.hsadminng.service.dto; -import java.time.LocalDate; -import javax.validation.constraints.*; -import java.io.Serializable; -import java.util.Objects; + import org.hostsharing.hsadminng.domain.enumeration.ShareAction; +import org.hostsharing.hsadminng.service.accessfilter.AccessFor; +import org.hostsharing.hsadminng.service.accessfilter.ParentId; +import org.hostsharing.hsadminng.service.accessfilter.Role; +import org.hostsharing.hsadminng.service.accessfilter.SelfId; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.time.LocalDate; +import java.util.Objects; /** * A DTO for the Share entity. */ public class ShareDTO implements Serializable { + @SelfId + @AccessFor(read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) private Long id; @NotNull + @AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) private LocalDate documentDate; @NotNull + @AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) private LocalDate valueDate; @NotNull + @AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) private ShareAction action; @NotNull + @AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) private Integer quantity; @Size(max = 160) + @AccessFor(init = Role.ADMIN, read = Role.SUPPORTER) private String remark; - + @ParentId(MembershipDTO.class) + @AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) private Long membershipId; + @AccessFor(init = Role.ADMIN, read = Role.SUPPORTER) private String membershipDocumentDate; public Long getId() { diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonBuilder.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonBuilder.java new file mode 100644 index 00000000..ec1d6487 --- /dev/null +++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonBuilder.java @@ -0,0 +1,27 @@ +package org.hostsharing.hsadminng.service.accessfilter; + +import org.apache.commons.lang3.tuple.ImmutablePair; + +public class JSonBuilder { + + @SafeVarargs + public static 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 static String inQuotes(Object value) { + return "\"" + value.toString() + "\""; + } + +} diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java index ed3fa7fb..9c0340b8 100644 --- a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java +++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java @@ -12,11 +12,13 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import org.springframework.context.ApplicationContext; 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.JSonBuilder.asJSon; import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenAuthenticatedUser; import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenUserHavingRole; import static org.mockito.BDDMockito.given; @@ -27,6 +29,9 @@ public class JSonDeserializerWithAccessFilterUnitTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Mock + public ApplicationContext ctx; + @Mock public JsonParser jsonParser; @@ -52,7 +57,7 @@ public class JSonDeserializerWithAccessFilterUnitTest { ImmutablePair.of("openStringField", "String Value"))); // when - GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize(); + GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDto.class).deserialize(); // then assertThat(actualDto.openStringField).isEqualTo("String Value"); @@ -66,7 +71,7 @@ public class JSonDeserializerWithAccessFilterUnitTest { ImmutablePair.of("openIntegerField", 1234))); // when - GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize(); + GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDto.class).deserialize(); // then assertThat(actualDto.openIntegerField).isEqualTo(1234); @@ -80,7 +85,7 @@ public class JSonDeserializerWithAccessFilterUnitTest { ImmutablePair.of("openLongField", 1234L))); // when - GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize(); + GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDto.class).deserialize(); // then assertThat(actualDto.openLongField).isEqualTo(1234L); @@ -96,7 +101,7 @@ public class JSonDeserializerWithAccessFilterUnitTest { ImmutablePair.of("restrictedField", "Restricted String Value"))); // when - GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize(); + GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDto.class).deserialize(); // then assertThat(actualDto.restrictedField).isEqualTo("Restricted String Value"); @@ -110,7 +115,7 @@ public class JSonDeserializerWithAccessFilterUnitTest { givenJSonTree(asJSon(ImmutablePair.of("restrictedField", "Restricted String Value"))); // when - Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize()); + Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDto.class).deserialize()); // then assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> { @@ -127,7 +132,7 @@ public class JSonDeserializerWithAccessFilterUnitTest { givenJSonTree(asJSon(ImmutablePair.of("parentId", 1111L))); // when - Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenChildDto.class).deserialize()); + Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenChildDto.class).deserialize()); // then assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> { @@ -144,7 +149,7 @@ public class JSonDeserializerWithAccessFilterUnitTest { givenJSonTree(asJSon(ImmutablePair.of("parentId", 1111L))); // when - final GivenChildDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenChildDto.class).deserialize(); + final GivenChildDto actualDto = new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenChildDto.class).deserialize(); // then assertThat(actualDto.parentId).isEqualTo(1111L); @@ -160,7 +165,7 @@ public class JSonDeserializerWithAccessFilterUnitTest { ImmutablePair.of("restrictedField", "Restricted String Value"))); // when - Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize()); + Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDto.class).deserialize()); // then assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> { @@ -175,7 +180,7 @@ public class JSonDeserializerWithAccessFilterUnitTest { givenJSonTree(asJSon(ImmutablePair.of("id", 1111L))); // when - Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDtoWithMultipleSelfId.class).deserialize()); + Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, GivenDtoWithMultipleSelfId.class).deserialize()); // then assertThat(exception).isInstanceOf(AssertionError.class).hasMessage("multiple @SelfId detected in GivenDtoWithMultipleSelfId"); @@ -183,30 +188,10 @@ public class JSonDeserializerWithAccessFilterUnitTest { // --- only fixture code below --- - @SafeVarargs - private final 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 void givenJSonTree(String givenJSon) throws IOException { given(codec.readTree(jsonParser)).willReturn(new ObjectMapper().readTree(givenJSon)); } - private String inQuotes(Object value) { - return "\"" + value.toString() + "\""; - } - public static class GivenDto { @SelfId diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java index 7d5265fc..8cd09db8 100644 --- a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java +++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java @@ -10,6 +10,7 @@ import org.junit.Test; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import org.springframework.context.ApplicationContext; import java.io.IOException; @@ -23,6 +24,9 @@ public class JSonSerializerWithAccessFilterUnitTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Mock + public ApplicationContext ctx; + @Mock public JsonGenerator jsonGenerator; @@ -37,7 +41,7 @@ public class JSonSerializerWithAccessFilterUnitTest { @Test public void shouldSerializeStringField() throws IOException { // when - new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize(); + new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize(); // then verify(jsonGenerator).writeStringField("openStringField", givenDTO.openStringField); @@ -51,7 +55,7 @@ public class JSonSerializerWithAccessFilterUnitTest { MockSecurityContext.givenUserHavingRole(GivenCustomerDto.class, 888L, Role.FINANCIAL_CONTACT); // when - new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize(); + new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize(); // then verify(jsonGenerator).writeStringField("restrictedField", givenDTO.restrictedField); @@ -65,7 +69,7 @@ public class JSonSerializerWithAccessFilterUnitTest { MockSecurityContext.givenUserHavingRole(GivenCustomerDto.class, 888L, Role.ANY_CUSTOMER_USER); // when - new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize(); + new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize(); // then verify(jsonGenerator, never()).writeStringField("restrictedField", givenDTO.restrictedField); @@ -84,7 +88,7 @@ public class JSonSerializerWithAccessFilterUnitTest { final GivenDtoWithUnimplementedFieldType givenDtoWithUnimplementedFieldType = new GivenDtoWithUnimplementedFieldType(); // when - Throwable actual = catchThrowable(() -> new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDtoWithUnimplementedFieldType).serialize()); + final Throwable actual = catchThrowable(() -> new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDtoWithUnimplementedFieldType).serialize()); // then assertThat(actual).isInstanceOf(NotImplementedException.class); diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/MockSecurityContext.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/MockSecurityContext.java index 5b3f78b2..cfa66edf 100644 --- a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/MockSecurityContext.java +++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/MockSecurityContext.java @@ -19,6 +19,9 @@ public class MockSecurityContext { } public static void givenUserHavingRole(final Class onClass, final Long onId, final Role role) { + if ((onClass == null || onId == null) && !role.isIndependent()) { + throw new IllegalArgumentException("dependent roles like " + role + " depend on DtoClass and ID"); + } SecurityUtils.addUserRole(onClass, onId, role); } } diff --git a/src/test/java/org/hostsharing/hsadminng/service/dto/MembershipDTOUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/dto/MembershipDTOUnitTest.java new file mode 100644 index 00000000..a4976186 --- /dev/null +++ b/src/test/java/org/hostsharing/hsadminng/service/dto/MembershipDTOUnitTest.java @@ -0,0 +1,85 @@ +package org.hostsharing.hsadminng.service.dto; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.hostsharing.hsadminng.service.accessfilter.JSonDeserializerWithAccessFilter; +import org.hostsharing.hsadminng.service.accessfilter.Role; +import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException; +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 org.springframework.context.ApplicationContext; + +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.JSonBuilder.asJSon; +import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenAuthenticatedUser; +import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenUserHavingRole; +import static org.mockito.BDDMockito.given; + +public class MembershipDTOUnitTest { + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + public ApplicationContext ctx; + + @Mock + public JsonParser jsonParser; + + @Mock + public ObjectCodec codec; + + @Mock + public TreeNode treeNode; + + @Before + public void init() { + given(jsonParser.getCodec()).willReturn(codec); + } + + @Test + public void adminShouldHaveRightToCreate() throws IOException { + givenAuthenticatedUser(); + givenUserHavingRole(null, null, Role.ADMIN); + givenJSonTree(asJSon(ImmutablePair.of("customerId", 1234L))); + + // when + final MembershipDTO actualDto = new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, MembershipDTO.class).deserialize(); + + // then + assertThat(actualDto.getCustomerId()).isEqualTo(1234L); + } + + @Test + public void contractualContactShouldNotHaveRightToCreate() throws IOException { + givenAuthenticatedUser(); + givenUserHavingRole(CustomerDTO.class, 1234L, Role.CONTRACTUAL_CONTACT); + givenJSonTree(asJSon(ImmutablePair.of("customerId", 1234L))); + + // when + Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, MembershipDTO.class).deserialize()); + + // then + assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> { + assertThat(badRequestAlertException.getParam()).isEqualTo("MembershipDTO.customerId"); + assertThat(badRequestAlertException.getErrorKey()).isEqualTo("referencingProhibited"); + }); + } + + // --- only fixture code below --- + + private void givenJSonTree(String givenJSon) throws IOException { + given(codec.readTree(jsonParser)).willReturn(new ObjectMapper().readTree(givenJSon)); + } + +} diff --git a/src/test/java/org/hostsharing/hsadminng/service/dto/ShareDTOUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/dto/ShareDTOUnitTest.java new file mode 100644 index 00000000..b42e6ac6 --- /dev/null +++ b/src/test/java/org/hostsharing/hsadminng/service/dto/ShareDTOUnitTest.java @@ -0,0 +1,163 @@ +package org.hostsharing.hsadminng.service.dto; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.hostsharing.hsadminng.domain.enumeration.ShareAction; +import org.hostsharing.hsadminng.service.CustomerService; +import org.hostsharing.hsadminng.service.MembershipService; +import org.hostsharing.hsadminng.service.accessfilter.JSonDeserializerWithAccessFilter; +import org.hostsharing.hsadminng.service.accessfilter.JSonSerializerWithAccessFilter; +import org.hostsharing.hsadminng.service.accessfilter.Role; +import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException; +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 org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ApplicationContext; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +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.givenUserHavingRole; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +public class ShareDTOUnitTest { + + private static final long SOME_MEMBERSHIP_ID = 12345L; + private static final long SOME_CUSTOMER_ID = 1234L; + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + + @Mock + private ApplicationContext ctx; + + @Mock + private AutowireCapableBeanFactory autowireCapableBeanFactory; + + @Mock + private JsonParser jsonParser; + + @Mock + private JsonGenerator jsonGenerator; + + @Mock + private ObjectCodec codec; + + @Mock + private TreeNode treeNode; + + @Mock + private CustomerService customerService; + + @Mock + private MembershipService membershipService; + + @Before + public void init() { + given(jsonParser.getCodec()).willReturn(codec); + + given(ctx.getAutowireCapableBeanFactory()).willReturn(autowireCapableBeanFactory); + given(ctx.getAutowireCapableBeanFactory()).willReturn(autowireCapableBeanFactory); + given(autowireCapableBeanFactory.createBean(CustomerService.class)).willReturn(customerService); + given(autowireCapableBeanFactory.createBean(MembershipService.class)).willReturn(membershipService); + + given(customerService.findOne(SOME_CUSTOMER_ID)).willReturn(Optional.of(new CustomerDTO())); + given(membershipService.findOne(SOME_MEMBERSHIP_ID)).willReturn(Optional.of(new MembershipDTO().with(dto -> dto.setCustomerId(SOME_CUSTOMER_ID)))); + } + + @Test + public void adminShouldHaveRightToCreate() throws IOException { + givenAuthenticatedUser(); + givenUserHavingRole(null, null, Role.ADMIN); + givenJSonTree(asJSon(ImmutablePair.of("membershipId", SOME_MEMBERSHIP_ID))); + + // when + final ShareDTO actualDto = new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, ShareDTO.class).deserialize(); + + // then + assertThat(actualDto.getMembershipId()).isEqualTo(SOME_MEMBERSHIP_ID); + } + + @Test + public void contractualContactShouldNotHaveRightToCreate() throws IOException { + givenAuthenticatedUser(); + givenUserHavingRole(CustomerDTO.class, SOME_CUSTOMER_ID, Role.CONTRACTUAL_CONTACT); + givenJSonTree(asJSon(ImmutablePair.of("membershipId", ShareDTOUnitTest.SOME_MEMBERSHIP_ID))); + + // when + Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(ctx, jsonParser, null, ShareDTO.class).deserialize()); + + // then + assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> { + assertThat(badRequestAlertException.getParam()).isEqualTo("ShareDTO.membershipId"); + assertThat(badRequestAlertException.getErrorKey()).isEqualTo("referencingProhibited"); + }); + } + + @Test + public void financialContactShouldHaveRightToReadAllButRemark() throws IOException { + givenAuthenticatedUser(); + givenUserHavingRole(CustomerDTO.class, SOME_CUSTOMER_ID, Role.FINANCIAL_CONTACT); + final ShareDTO givenDTO = createShareDto(); + + // when + new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize(); + + // then + verify(jsonGenerator).writeNumberField("id", givenDTO.getId()); + verify(jsonGenerator).writeNumberField("membershipId", givenDTO.getMembershipId()); + verify(jsonGenerator, never()).writeStringField(eq("remark"), anyString()); + } + + @Test + public void supporterShouldHaveRightToRead() throws IOException { + givenAuthenticatedUser(); + givenUserHavingRole(null, null, Role.SUPPORTER); + final ShareDTO givenDTO = createShareDto(); + + // when + new JSonSerializerWithAccessFilter<>(ctx, jsonGenerator, null, givenDTO).serialize(); + + // then + verify(jsonGenerator).writeNumberField("id", givenDTO.getId()); + verify(jsonGenerator).writeNumberField("membershipId", givenDTO.getMembershipId()); + verify(jsonGenerator).writeStringField("remark", givenDTO.getRemark()); + } + + // --- only fixture code below --- + + private void givenJSonTree(String givenJSon) throws IOException { + given(codec.readTree(jsonParser)).willReturn(new ObjectMapper().readTree(givenJSon)); + } + + private ShareDTO createShareDto() { + final ShareDTO givenDTO = new ShareDTO(); + givenDTO.setId(1234567L); + givenDTO.setMembershipId(SOME_MEMBERSHIP_ID); + givenDTO.setAction(ShareAction.SUBSCRIPTION); + givenDTO.setQuantity(3); + givenDTO.setDocumentDate(LocalDate.parse("2019-04-22")); + givenDTO.setMembershipDocumentDate("2019-04-21"); // TODO: why is this not a LocalDate? + givenDTO.setValueDate(LocalDate.parse("2019-04-30")); + givenDTO.setRemark("Some Remark"); + return givenDTO; + } + +}