From 639ea062431ad2e08b83904741612005196eea05 Mon Sep 17 00:00:00 2001
From: Michael Hoennig <michael@hoennig.de>
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<Membership> memberships = new HashSet<>();
+
     @OneToMany(mappedBy = "customer")
     private Set<SepaMandate> 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<SepaMandate> 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<Share> shares = new HashSet<>();
+
     @OneToMany(mappedBy = "membership")
     private Set<Asset> 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<CustomerDTO> {
 
     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<T> {
+    Optional<? extends T> 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<MembershipDTO> {
 
     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<MembershipDTO> 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<T> {
+    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<T> {
         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<CustomerDTO>
+        if ( parentDtoClass == MembershipDTO.class ) {
+            final DtoLoader<MembershipDTO> dtoLoader = ctx.getAutowireCapableBeanFactory().createBean(MembershipService.class);
+            return dtoLoader.findOne(parentId).get();
+        }
+        if ( parentDtoClass == CustomerDTO.class ) {
+            final DtoLoader<CustomerDTO> 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<? extends Annotation> 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<T> extends JSonAccessFilter<T> {
     private final TreeNode treeNode;
     private final Set<Field> modifiedFields = new HashSet<>();
 
-    public JSonDeserializerWithAccessFilter(final JsonParser jsonParser, final DeserializationContext deserializationContext, Class<T> dtoClass) {
-        super(unchecked(dtoClass::newInstance));
+    public JSonDeserializerWithAccessFilter(final ApplicationContext ctx, final JsonParser jsonParser, final DeserializationContext deserializationContext, Class<T> 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 <T> extends JSonAccessFilter<T> {
     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 <T> extends JSonAccessFilter<T> {
                 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<CustomerDTO> {
 
+        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<CustomerDTO> {
 
+        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<String, Object>... properties) {
+        final StringBuilder json = new StringBuilder();
+        for (ImmutablePair<String, Object> 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<String, Object>... properties) {
-        final StringBuilder json = new StringBuilder();
-        for (ImmutablePair<String, Object> 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;
+    }
+
+}