JSonAccessFilter with generic access to grand parent role
This commit is contained in:
parent
639ea06243
commit
ad1517a16e
@ -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})
|
||||
|
Loading…
Reference in New Issue
Block a user