JSonAccessFilter with generic access to grand parent role

This commit is contained in:
Michael Hoennig 2019-04-24 13:23:08 +02:00
parent 639ea06243
commit ad1517a16e
9 changed files with 48 additions and 29 deletions

View File

@ -18,7 +18,7 @@ import java.util.Optional;
*/ */
@Service @Service
@Transactional @Transactional
public class CustomerService implements DtoLoader<CustomerDTO> { public class CustomerService implements IdToDtoResolver<CustomerDTO> {
private final Logger log = LoggerFactory.getLogger(CustomerService.class); private final Logger log = LoggerFactory.getLogger(CustomerService.class);

View File

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

View File

@ -19,7 +19,7 @@ import java.util.Optional;
*/ */
@Service @Service
@Transactional @Transactional
public class MembershipService implements DtoLoader<MembershipDTO> { public class MembershipService implements IdToDtoResolver<MembershipDTO> {
private final Logger log = LoggerFactory.getLogger(MembershipService.class); private final Logger log = LoggerFactory.getLogger(MembershipService.class);

View File

@ -1,17 +1,16 @@
package org.hostsharing.hsadminng.service.accessfilter; package org.hostsharing.hsadminng.service.accessfilter;
import org.apache.commons.lang3.NotImplementedException;
import org.hostsharing.hsadminng.security.SecurityUtils; import org.hostsharing.hsadminng.security.SecurityUtils;
import org.hostsharing.hsadminng.service.CustomerService; import org.hostsharing.hsadminng.service.IdToDtoResolver;
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.dto.MembershipDTO;
import org.hostsharing.hsadminng.service.util.ReflectionUtil; import org.hostsharing.hsadminng.service.util.ReflectionUtil;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import javax.persistence.EntityNotFoundException;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
abstract class JSonAccessFilter<T> { abstract class JSonAccessFilter<T> {
private final ApplicationContext ctx; private final ApplicationContext ctx;
@ -50,43 +49,51 @@ abstract class JSonAccessFilter<T> {
*/ */
Role getLoginUserRole() { Role getLoginUserRole() {
final Role roleOnSelf = getLoginUserRoleOnSelf(); final Role roleOnSelf = getLoginUserRoleOnSelf();
if ( roleOnSelf.isIndependent() ) { if (roleOnSelf.isIndependent()) {
return roleOnSelf; return roleOnSelf;
} }
return getLoginUserRoleOnAncestorOfDtoClassIfHigher(roleOnSelf, dto); return getLoginUserRoleOnAncestorOfDtoClassIfHigher(roleOnSelf, dto);
} }
private Role getLoginUserRoleOnSelf() { private Role getLoginUserRoleOnSelf() {
return SecurityUtils.getLoginUserRoleFor(dto.getClass(), getId() ); return SecurityUtils.getLoginUserRoleFor(dto.getClass(), getId());
} }
private Role getLoginUserRoleOnAncestorOfDtoClassIfHigher(final Role baseRole, final Object dto) { private Role getLoginUserRoleOnAncestorOfDtoClassIfHigher(final Role baseRole, final Object dto) {
final Field parentIdField = determineFieldWithAnnotation(dto.getClass(), ParentId.class); final Field parentIdField = determineFieldWithAnnotation(dto.getClass(), ParentId.class);
if ( parentIdField == null ) { if (parentIdField == null) {
return baseRole; return baseRole;
} }
final ParentId parentIdAnnot = parentIdField.getAnnotation(ParentId.class); final ParentId parentIdAnnot = parentIdField.getAnnotation(ParentId.class);
final Class<?> parentDtoClass = parentIdAnnot.value(); final Class<? extends IdToDtoResolver> parentDtoLoader = parentIdAnnot.resolver();
final Class<?> parentDtoClass = getGenericClassParameter(parentDtoLoader);
final Long parentId = (Long) ReflectionUtil.getValue(dto, parentIdField); final Long parentId = (Long) ReflectionUtil.getValue(dto, parentIdField);
final Role roleOnParent = SecurityUtils.getLoginUserRoleFor(parentDtoClass, parentId); final Role roleOnParent = SecurityUtils.getLoginUserRoleFor(parentDtoClass, parentId);
final Object parentEntity = findParentDto(parentDtoClass, parentId); final Object parentEntity = findParentDto(parentDtoLoader, parentId);
return Role.broadest(baseRole, getLoginUserRoleOnAncestorOfDtoClassIfHigher(roleOnParent, parentEntity)); return Role.broadest(baseRole, getLoginUserRoleOnAncestorOfDtoClassIfHigher(roleOnParent, parentEntity));
} }
private Object findParentDto(Class<?> parentDtoClass, Long parentId) { @SuppressWarnings("unchecked")
// TODO: generalize, e.g. via "all beans that implement DtoLoader<CustomerDTO> private Class<T> getGenericClassParameter(Class<? extends IdToDtoResolver> parentDtoLoader) {
if ( parentDtoClass == MembershipDTO.class ) { for (Type genericInterface : parentDtoLoader.getGenericInterfaces()) {
final DtoLoader<MembershipDTO> dtoLoader = ctx.getAutowireCapableBeanFactory().createBean(MembershipService.class); if (genericInterface instanceof ParameterizedType) {
return dtoLoader.findOne(parentId).get(); final ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
if (parameterizedType.getRawType()== IdToDtoResolver.class) {
return (Class<T>) parameterizedType.getActualTypeArguments()[0];
}
}
} }
if ( parentDtoClass == CustomerDTO.class ) { throw new AssertionError(parentDtoLoader.getSimpleName() + " expected to implement " + IdToDtoResolver.class.getSimpleName() + "<...DTO>");
final DtoLoader<CustomerDTO> dtoLoader = ctx.getAutowireCapableBeanFactory().createBean(CustomerService.class); }
return dtoLoader.findOne(parentId).get();
} @SuppressWarnings("unchecked")
throw new NotImplementedException("no DtoLoader implemented for " + parentDtoClass); private Object findParentDto(final Class<? extends IdToDtoResolver> parentDtoLoader, final Long parentId) {
final IdToDtoResolver<MembershipDTO> idToDtoResolver = ctx.getAutowireCapableBeanFactory().createBean(parentDtoLoader);
return idToDtoResolver.findOne(parentId).orElseThrow(() -> new EntityNotFoundException("Can't resolve parent entity ID " + parentId + " via " + parentDtoLoader));
} }
private static Field determineFieldWithAnnotation(final Class<?> dtoClass, final Class<? extends Annotation> idAnnotationClass) { private static Field determineFieldWithAnnotation(final Class<?> dtoClass, final Class<? extends Annotation> idAnnotationClass) {

View File

@ -1,5 +1,7 @@
package org.hostsharing.hsadminng.service.accessfilter; package org.hostsharing.hsadminng.service.accessfilter;
import org.hostsharing.hsadminng.service.IdToDtoResolver;
import java.lang.annotation.*; import java.lang.annotation.*;
/** /**
@ -12,6 +14,6 @@ import java.lang.annotation.*;
@Target({ElementType.FIELD}) @Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface ParentId { public @interface ParentId {
/// The DTO class of the referenced entity. /// The service which can load the referenced DTO.
Class<?> value(); Class<? extends IdToDtoResolver<?>> resolver();
} }

View File

@ -1,5 +1,6 @@
package org.hostsharing.hsadminng.service.dto; package org.hostsharing.hsadminng.service.dto;
import org.hostsharing.hsadminng.service.CustomerService;
import org.hostsharing.hsadminng.service.accessfilter.AccessFor; import org.hostsharing.hsadminng.service.accessfilter.AccessFor;
import org.hostsharing.hsadminng.service.accessfilter.ParentId; import org.hostsharing.hsadminng.service.accessfilter.ParentId;
import org.hostsharing.hsadminng.service.accessfilter.Role; import org.hostsharing.hsadminng.service.accessfilter.Role;
@ -36,7 +37,7 @@ public class MembershipDTO implements Serializable {
@AccessFor(init = Role.ADMIN, read = Role.SUPPORTER) @AccessFor(init = Role.ADMIN, read = Role.SUPPORTER)
private String remark; private String remark;
@ParentId(CustomerDTO.class) @ParentId(resolver = CustomerService.class)
@AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) @AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
private Long customerId; private Long customerId;

View File

@ -1,6 +1,7 @@
package org.hostsharing.hsadminng.service.dto; package org.hostsharing.hsadminng.service.dto;
import org.hostsharing.hsadminng.domain.enumeration.ShareAction; import org.hostsharing.hsadminng.domain.enumeration.ShareAction;
import org.hostsharing.hsadminng.service.MembershipService;
import org.hostsharing.hsadminng.service.accessfilter.AccessFor; import org.hostsharing.hsadminng.service.accessfilter.AccessFor;
import org.hostsharing.hsadminng.service.accessfilter.ParentId; import org.hostsharing.hsadminng.service.accessfilter.ParentId;
import org.hostsharing.hsadminng.service.accessfilter.Role; import org.hostsharing.hsadminng.service.accessfilter.Role;
@ -41,7 +42,7 @@ public class ShareDTO implements Serializable {
@AccessFor(init = Role.ADMIN, read = Role.SUPPORTER) @AccessFor(init = Role.ADMIN, read = Role.SUPPORTER)
private String remark; private String remark;
@ParentId(MembershipDTO.class) @ParentId(resolver = MembershipService.class)
@AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) @AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
private Long membershipId; private Long membershipId;

View File

@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.ImmutablePair;
import org.hostsharing.hsadminng.service.IdToDtoResolver;
import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException; import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
@ -211,6 +212,9 @@ public class JSonDeserializerWithAccessFilterUnitTest {
Long openLongField; Long openLongField;
} }
abstract class GivenService implements IdToDtoResolver<GivenDto> {
}
public static class GivenChildDto { public static class GivenChildDto {
@SelfId @SelfId
@ -218,7 +222,7 @@ public class JSonDeserializerWithAccessFilterUnitTest {
Long id; Long id;
@AccessFor(init = Role.CONTRACTUAL_CONTACT, update = Role.CONTRACTUAL_CONTACT, read = Role.ACTUAL_CUSTOMER_USER) @AccessFor(init = Role.CONTRACTUAL_CONTACT, update = Role.CONTRACTUAL_CONTACT, read = Role.ACTUAL_CUSTOMER_USER)
@ParentId(GivenDto.class) @ParentId(resolver = GivenService.class)
Long parentId; Long parentId;
@AccessFor(init = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT}, update = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT}) @AccessFor(init = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT}, update = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT})

View File

@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonGenerator;
import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.RandomUtils;
import org.hostsharing.hsadminng.service.IdToDtoResolver;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -110,9 +111,12 @@ public class JSonSerializerWithAccessFilterUnitTest {
} }
private abstract class GivenCustomerService implements IdToDtoResolver<GivenCustomerDto> {
}
private static class GivenDto { private static class GivenDto {
@ParentId(GivenCustomerDto.class) @ParentId(resolver = GivenCustomerService.class)
Long customerId; Long customerId;
@AccessFor(read = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT}) @AccessFor(read = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT})