1
0

JSonAccessFilter with initially working (hardcoded) grand parent role

This commit is contained in:
Michael Hoennig
2019-04-24 12:30:26 +02:00
parent a94516b3ce
commit 639ea06243
18 changed files with 468 additions and 87 deletions

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -0,0 +1,7 @@
package org.hostsharing.hsadminng.service;
import java.util.Optional;
public interface DtoLoader<T> {
Optional<? extends T> findOne(Long id);
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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));
}

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -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(

View File

@ -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() {