JSonAccessFilter with generic access to grand parent role
This commit is contained in:
		@@ -18,7 +18,7 @@ import java.util.Optional;
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
@Transactional
 | 
			
		||||
public class CustomerService implements DtoLoader<CustomerDTO> {
 | 
			
		||||
public class CustomerService implements IdToDtoResolver<CustomerDTO> {
 | 
			
		||||
 | 
			
		||||
    private final Logger log = LoggerFactory.getLogger(CustomerService.class);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,6 @@ package org.hostsharing.hsadminng.service;
 | 
			
		||||
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
public interface DtoLoader<T> {
 | 
			
		||||
public interface IdToDtoResolver<T> {
 | 
			
		||||
    Optional<? extends T> findOne(Long id);
 | 
			
		||||
}
 | 
			
		||||
@@ -19,7 +19,7 @@ import java.util.Optional;
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
@Transactional
 | 
			
		||||
public class MembershipService implements DtoLoader<MembershipDTO> {
 | 
			
		||||
public class MembershipService implements IdToDtoResolver<MembershipDTO> {
 | 
			
		||||
 | 
			
		||||
    private final Logger log = LoggerFactory.getLogger(MembershipService.class);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,16 @@
 | 
			
		||||
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.IdToDtoResolver;
 | 
			
		||||
import org.hostsharing.hsadminng.service.dto.MembershipDTO;
 | 
			
		||||
import org.hostsharing.hsadminng.service.util.ReflectionUtil;
 | 
			
		||||
import org.springframework.context.ApplicationContext;
 | 
			
		||||
 | 
			
		||||
import javax.persistence.EntityNotFoundException;
 | 
			
		||||
import java.lang.annotation.Annotation;
 | 
			
		||||
import java.lang.reflect.Field;
 | 
			
		||||
import java.lang.reflect.ParameterizedType;
 | 
			
		||||
import java.lang.reflect.Type;
 | 
			
		||||
 | 
			
		||||
abstract class JSonAccessFilter<T> {
 | 
			
		||||
    private final ApplicationContext ctx;
 | 
			
		||||
@@ -50,43 +49,51 @@ abstract class JSonAccessFilter<T> {
 | 
			
		||||
     */
 | 
			
		||||
    Role getLoginUserRole() {
 | 
			
		||||
        final Role roleOnSelf = getLoginUserRoleOnSelf();
 | 
			
		||||
        if ( roleOnSelf.isIndependent() ) {
 | 
			
		||||
        if (roleOnSelf.isIndependent()) {
 | 
			
		||||
            return roleOnSelf;
 | 
			
		||||
        }
 | 
			
		||||
        return getLoginUserRoleOnAncestorOfDtoClassIfHigher(roleOnSelf, dto);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Role getLoginUserRoleOnSelf() {
 | 
			
		||||
        return SecurityUtils.getLoginUserRoleFor(dto.getClass(), getId() );
 | 
			
		||||
        return SecurityUtils.getLoginUserRoleFor(dto.getClass(), getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Role getLoginUserRoleOnAncestorOfDtoClassIfHigher(final Role baseRole, final Object dto) {
 | 
			
		||||
        final Field parentIdField = determineFieldWithAnnotation(dto.getClass(), ParentId.class);
 | 
			
		||||
 | 
			
		||||
        if ( parentIdField == null ) {
 | 
			
		||||
        if (parentIdField == null) {
 | 
			
		||||
            return baseRole;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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 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));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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();
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    private Class<T> getGenericClassParameter(Class<? extends IdToDtoResolver> parentDtoLoader) {
 | 
			
		||||
        for (Type genericInterface : parentDtoLoader.getGenericInterfaces()) {
 | 
			
		||||
            if (genericInterface instanceof ParameterizedType) {
 | 
			
		||||
                final ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
 | 
			
		||||
                if (parameterizedType.getRawType()== IdToDtoResolver.class) {
 | 
			
		||||
                    return (Class<T>) parameterizedType.getActualTypeArguments()[0];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        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);
 | 
			
		||||
        throw new AssertionError(parentDtoLoader.getSimpleName() + " expected to implement " + IdToDtoResolver.class.getSimpleName() + "<...DTO>");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("unchecked")
 | 
			
		||||
    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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
package org.hostsharing.hsadminng.service.accessfilter;
 | 
			
		||||
 | 
			
		||||
import org.hostsharing.hsadminng.service.IdToDtoResolver;
 | 
			
		||||
 | 
			
		||||
import java.lang.annotation.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -12,6 +14,6 @@ import java.lang.annotation.*;
 | 
			
		||||
@Target({ElementType.FIELD})
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
public @interface ParentId {
 | 
			
		||||
    /// The DTO class of the referenced entity.
 | 
			
		||||
    Class<?> value();
 | 
			
		||||
    /// The service which can load the referenced DTO.
 | 
			
		||||
    Class<? extends IdToDtoResolver<?>> resolver();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
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.ParentId;
 | 
			
		||||
import org.hostsharing.hsadminng.service.accessfilter.Role;
 | 
			
		||||
@@ -36,7 +37,7 @@ public class MembershipDTO implements Serializable {
 | 
			
		||||
    @AccessFor(init = Role.ADMIN, read = Role.SUPPORTER)
 | 
			
		||||
    private String remark;
 | 
			
		||||
 | 
			
		||||
    @ParentId(CustomerDTO.class)
 | 
			
		||||
    @ParentId(resolver = CustomerService.class)
 | 
			
		||||
    @AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
 | 
			
		||||
    private Long customerId;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package org.hostsharing.hsadminng.service.dto;
 | 
			
		||||
 | 
			
		||||
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.ParentId;
 | 
			
		||||
import org.hostsharing.hsadminng.service.accessfilter.Role;
 | 
			
		||||
@@ -41,7 +42,7 @@ public class ShareDTO implements Serializable {
 | 
			
		||||
    @AccessFor(init = Role.ADMIN, read = Role.SUPPORTER)
 | 
			
		||||
    private String remark;
 | 
			
		||||
 | 
			
		||||
    @ParentId(MembershipDTO.class)
 | 
			
		||||
    @ParentId(resolver = MembershipService.class)
 | 
			
		||||
    @AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
 | 
			
		||||
    private Long membershipId;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ 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.IdToDtoResolver;
 | 
			
		||||
import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Rule;
 | 
			
		||||
@@ -211,6 +212,9 @@ public class JSonDeserializerWithAccessFilterUnitTest {
 | 
			
		||||
        Long openLongField;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    abstract class GivenService implements IdToDtoResolver<GivenDto> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class GivenChildDto {
 | 
			
		||||
 | 
			
		||||
        @SelfId
 | 
			
		||||
@@ -218,7 +222,7 @@ public class JSonDeserializerWithAccessFilterUnitTest {
 | 
			
		||||
        Long id;
 | 
			
		||||
 | 
			
		||||
        @AccessFor(init = Role.CONTRACTUAL_CONTACT, update = Role.CONTRACTUAL_CONTACT, read = Role.ACTUAL_CUSTOMER_USER)
 | 
			
		||||
        @ParentId(GivenDto.class)
 | 
			
		||||
        @ParentId(resolver = GivenService.class)
 | 
			
		||||
        Long parentId;
 | 
			
		||||
 | 
			
		||||
        @AccessFor(init = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT}, update = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT})
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonGenerator;
 | 
			
		||||
import org.apache.commons.lang3.NotImplementedException;
 | 
			
		||||
import org.apache.commons.lang3.RandomStringUtils;
 | 
			
		||||
import org.apache.commons.lang3.RandomUtils;
 | 
			
		||||
import org.hostsharing.hsadminng.service.IdToDtoResolver;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Rule;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
@@ -110,9 +111,12 @@ public class JSonSerializerWithAccessFilterUnitTest {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private abstract class GivenCustomerService implements IdToDtoResolver<GivenCustomerDto> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static class GivenDto {
 | 
			
		||||
 | 
			
		||||
        @ParentId(GivenCustomerDto.class)
 | 
			
		||||
        @ParentId(resolver = GivenCustomerService.class)
 | 
			
		||||
        Long customerId;
 | 
			
		||||
 | 
			
		||||
        @AccessFor(read = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT})
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user