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
|
@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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
@ -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);
|
||||||
|
|
||||||
|
@ -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 ) {
|
|
||||||
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) {
|
private static Field determineFieldWithAnnotation(final Class<?> dtoClass, final Class<? extends Annotation> idAnnotationClass) {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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})
|
||||||
|
@ -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})
|
||||||
|
Loading…
Reference in New Issue
Block a user