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
@Transactional
public class CustomerService implements DtoLoader<CustomerDTO> {
public class CustomerService implements IdToDtoResolver<CustomerDTO> {
private final Logger log = LoggerFactory.getLogger(CustomerService.class);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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