JSonSerializer/DeserializerWithAccessFilter: also use role in parent
This commit is contained in:
parent
1505e7bd66
commit
a94516b3ce
@ -1,9 +1,12 @@
|
|||||||
package org.hostsharing.hsadminng.security;
|
package org.hostsharing.hsadminng.security;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.service.accessfilter.Role;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -11,6 +14,8 @@ import java.util.Optional;
|
|||||||
*/
|
*/
|
||||||
public final class SecurityUtils {
|
public final class SecurityUtils {
|
||||||
|
|
||||||
|
private static List<UserRoleAssignment> userRoleAssignments = new ArrayList<>();
|
||||||
|
|
||||||
private SecurityUtils() {
|
private SecurityUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,4 +78,42 @@ public final class SecurityUtils {
|
|||||||
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority)))
|
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority)))
|
||||||
.orElse(false);
|
.orElse(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Role getLoginUserRoleFor(final Class<?> onDtoClass, final Long onId) {
|
||||||
|
|
||||||
|
final Role highestRole = userRoleAssignments.stream().
|
||||||
|
map(ura ->
|
||||||
|
matches(onDtoClass, onId, ura)
|
||||||
|
? ura.role
|
||||||
|
: Role.ANYBODY).
|
||||||
|
reduce(Role.ANYBODY, (r1, r2) -> r1.covers(r2) ? r1 : r2);
|
||||||
|
return highestRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean matches(Class<?> onDtoClass, Long onId, UserRoleAssignment ura) {
|
||||||
|
final boolean matches = (ura.onClass == null || onDtoClass == ura.onClass) && (ura.onId == null || onId.equals(ura.onId) );
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: depends on https://plan.hostsharing.net/project/hsadmin/us/67?milestone=34
|
||||||
|
public static void addUserRole(final Class<?> onClass, final Long onId, final Role role) {
|
||||||
|
userRoleAssignments.add(new UserRoleAssignment(onClass, onId, role));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearUserRoles() {
|
||||||
|
userRoleAssignments.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class UserRoleAssignment {
|
||||||
|
final Class<?> onClass;
|
||||||
|
final Long onId;
|
||||||
|
final Role role;
|
||||||
|
|
||||||
|
UserRoleAssignment(Class<?> onClass, Long onId, Role role) {
|
||||||
|
this.onClass = onClass;
|
||||||
|
this.onId = onId;
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
package org.hostsharing.hsadminng.service.accessfilter;
|
||||||
|
|
||||||
|
import org.hostsharing.hsadminng.security.SecurityUtils;
|
||||||
|
import org.hostsharing.hsadminng.service.util.ReflectionUtil;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
abstract class JSonAccessFilter<T> {
|
||||||
|
final T dto;
|
||||||
|
Field selfIdField = null;
|
||||||
|
Field parentIdField = null;
|
||||||
|
|
||||||
|
JSonAccessFilter(final T dto) {
|
||||||
|
this.dto = dto;
|
||||||
|
determineIdFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
void determineIdFields() {
|
||||||
|
for (Field field : dto.getClass().getDeclaredFields()) {
|
||||||
|
if (field.isAnnotationPresent(SelfId.class)) {
|
||||||
|
if (selfIdField != null) {
|
||||||
|
throw new AssertionError("multiple @" + SelfId.class.getSimpleName() + " detected in " + field.getDeclaringClass().getSimpleName());
|
||||||
|
}
|
||||||
|
selfIdField = field;
|
||||||
|
}
|
||||||
|
if (field.isAnnotationPresent(ParentId.class)) {
|
||||||
|
if (parentIdField != null) {
|
||||||
|
throw new AssertionError("multiple @" + ParentId.class.getSimpleName() + " detected in " + field.getDeclaringClass().getSimpleName());
|
||||||
|
}
|
||||||
|
parentIdField = field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Long getId() {
|
||||||
|
if (selfIdField == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (Long) ReflectionUtil.getValue(dto, selfIdField);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toDisplay(final Field field) {
|
||||||
|
return field.getDeclaringClass().getSimpleName() + "." + field.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
Role getLoginUserRole() {
|
||||||
|
final Role roleOnSelf = getLoginUserRoleOnSelf();
|
||||||
|
final Role roleOnParent = getLoginUserRoleOnParent();
|
||||||
|
return roleOnSelf.covers(roleOnParent) ? roleOnSelf : roleOnParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Role getLoginUserRoleOnSelf() {
|
||||||
|
// TODO: find broadest role in self and recursively in parent
|
||||||
|
return SecurityUtils.getLoginUserRoleFor(dto.getClass(), getId() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private Role getLoginUserRoleOnParent() {
|
||||||
|
if ( parentIdField == null ) {
|
||||||
|
return Role.ANYBODY;
|
||||||
|
}
|
||||||
|
final ParentId parentId = parentIdField.getAnnotation(ParentId.class);
|
||||||
|
return SecurityUtils.getLoginUserRoleFor(parentId.value(), (Long) ReflectionUtil.getValue(dto, parentIdField) );
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ import com.fasterxml.jackson.databind.node.IntNode;
|
|||||||
import com.fasterxml.jackson.databind.node.LongNode;
|
import com.fasterxml.jackson.databind.node.LongNode;
|
||||||
import com.fasterxml.jackson.databind.node.TextNode;
|
import com.fasterxml.jackson.databind.node.TextNode;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import org.hostsharing.hsadminng.security.SecurityUtils;
|
|
||||||
import org.hostsharing.hsadminng.service.util.ReflectionUtil;
|
import org.hostsharing.hsadminng.service.util.ReflectionUtil;
|
||||||
import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException;
|
import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException;
|
||||||
|
|
||||||
@ -17,37 +16,23 @@ import java.util.Set;
|
|||||||
|
|
||||||
import static org.hostsharing.hsadminng.service.util.ReflectionUtil.unchecked;
|
import static org.hostsharing.hsadminng.service.util.ReflectionUtil.unchecked;
|
||||||
|
|
||||||
public class JSonDeserializerWithAccessFilter<T> {
|
public class JSonDeserializerWithAccessFilter<T> extends JSonAccessFilter<T> {
|
||||||
|
|
||||||
private final T dto;
|
|
||||||
private final TreeNode treeNode;
|
private final TreeNode treeNode;
|
||||||
private final Set<Field> modifiedFields = new HashSet<>();
|
private final Set<Field> modifiedFields = new HashSet<>();
|
||||||
private Field selfIdField = null;
|
|
||||||
|
|
||||||
public JSonDeserializerWithAccessFilter(final JsonParser jsonParser, final DeserializationContext deserializationContext, Class<T> dtoClass) {
|
public JSonDeserializerWithAccessFilter(final JsonParser jsonParser, final DeserializationContext deserializationContext, Class<T> dtoClass) {
|
||||||
|
super(unchecked(dtoClass::newInstance));
|
||||||
this.treeNode = unchecked(() -> jsonParser.getCodec().readTree(jsonParser));
|
this.treeNode = unchecked(() -> jsonParser.getCodec().readTree(jsonParser));
|
||||||
this.dto = unchecked(dtoClass::newInstance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jackson deserializes from the JsonParser, thus no input parameter needed.
|
// Jackson deserializes from the JsonParser, thus no input parameter needed.
|
||||||
public T deserialize() {
|
public T deserialize() {
|
||||||
determineSelfIdField();
|
|
||||||
deserializeValues();
|
deserializeValues();
|
||||||
checkAccessToModifiedFields();
|
checkAccessToModifiedFields();
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void determineSelfIdField() {
|
|
||||||
for (Field field : dto.getClass().getDeclaredFields()) {
|
|
||||||
if (field.isAnnotationPresent(SelfId.class)) {
|
|
||||||
if (selfIdField != null) {
|
|
||||||
throw new AssertionError("multiple @" + SelfId.class.getSimpleName() + " detected in " + field.getDeclaringClass().getSimpleName());
|
|
||||||
}
|
|
||||||
selfIdField = field;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deserializeValues() {
|
private void deserializeValues() {
|
||||||
treeNode.fieldNames().forEachRemaining(fieldName -> {
|
treeNode.fieldNames().forEachRemaining(fieldName -> {
|
||||||
try {
|
try {
|
||||||
@ -90,34 +75,21 @@ public class JSonDeserializerWithAccessFilter<T> {
|
|||||||
modifiedFields.add(field);
|
modifiedFields.add(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object getId() {
|
|
||||||
if (selfIdField == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return ReflectionUtil.getValue(dto, selfIdField);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkAccessToModifiedFields() {
|
private void checkAccessToModifiedFields() {
|
||||||
modifiedFields.forEach(field -> {
|
modifiedFields.forEach(field -> {
|
||||||
if ( !field.equals(selfIdField) ) {
|
if ( !field.equals(selfIdField) ) {
|
||||||
if (getId() == null) {
|
if (getId() == null) {
|
||||||
if (!getLoginUserRole().isAllowedToInit(field)) {
|
if (!getLoginUserRole().isAllowedToInit(field)) {
|
||||||
throw new BadRequestAlertException("Initialization of field prohibited for current user", toDisplay(field), "initializationProhibited");
|
if ( !field.equals(parentIdField)) {
|
||||||
|
throw new BadRequestAlertException("Initialization of field prohibited for current user", toDisplay(field), "initializationProhibited");
|
||||||
|
} else {
|
||||||
|
throw new BadRequestAlertException("Referencing field prohibited for current user", toDisplay(field), "referencingProhibited");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (getId() != null) {
|
} else if (!getLoginUserRole().isAllowedToUpdate(field)) {
|
||||||
if (!getLoginUserRole().isAllowedToUpdate(field)) {
|
|
||||||
throw new BadRequestAlertException("Update of field prohibited for current user", toDisplay(field), "updateProhibited");
|
throw new BadRequestAlertException("Update of field prohibited for current user", toDisplay(field), "updateProhibited");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private String toDisplay(final Field field) {
|
|
||||||
return field.getDeclaringClass().getSimpleName() + "." + field.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Role getLoginUserRole() {
|
|
||||||
return SecurityUtils.getCurrentUserLogin().map(u -> Role.valueOf(u.toUpperCase())).orElse(Role.ANYBODY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,28 +2,22 @@ package org.hostsharing.hsadminng.service.accessfilter;
|
|||||||
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
import org.apache.commons.lang3.NotImplementedException;
|
import org.apache.commons.lang3.NotImplementedException;
|
||||||
import org.hostsharing.hsadminng.security.SecurityUtils;
|
|
||||||
import org.springframework.boot.jackson.JsonComponent;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
public class JSonSerializerWithAccessFilter <T> {
|
public class JSonSerializerWithAccessFilter <T> extends JSonAccessFilter<T> {
|
||||||
private final JsonGenerator jsonGenerator;
|
private final JsonGenerator jsonGenerator;
|
||||||
private final SerializerProvider serializerProvider;
|
private final SerializerProvider serializerProvider;
|
||||||
private final T dto;
|
|
||||||
|
|
||||||
public JSonSerializerWithAccessFilter(final JsonGenerator jsonGenerator,
|
public JSonSerializerWithAccessFilter(final JsonGenerator jsonGenerator,
|
||||||
final SerializerProvider serializerProvider,
|
final SerializerProvider serializerProvider,
|
||||||
final T dto) {
|
final T dto) {
|
||||||
|
super(dto);
|
||||||
this.jsonGenerator = jsonGenerator;
|
this.jsonGenerator = jsonGenerator;
|
||||||
this.serializerProvider = serializerProvider;
|
this.serializerProvider = serializerProvider;
|
||||||
this.dto = dto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Jackson serializes into the JsonGenerator, thus no return value needed.
|
// Jackson serializes into the JsonGenerator, thus no return value needed.
|
||||||
@ -65,7 +59,4 @@ public class JSonSerializerWithAccessFilter <T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Role getLoginUserRole() {
|
|
||||||
return SecurityUtils.getCurrentUserLogin().map(u -> Role.valueOf(u.toUpperCase())).orElse(Role.ANYBODY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package org.hostsharing.hsadminng.service.accessfilter;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to mark a field within a DTO as containing the id of a referenced entity,
|
||||||
|
* it's needed to determine access rights for entity creation.
|
||||||
|
*
|
||||||
|
* @see AccessFor
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Target({ElementType.FIELD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ParentId {
|
||||||
|
/// The DTO class of the referenced entity.
|
||||||
|
Class<?> value();
|
||||||
|
}
|
@ -1,7 +1,5 @@
|
|||||||
package org.hostsharing.hsadminng.service.accessfilter;
|
package org.hostsharing.hsadminng.service.accessfilter;
|
||||||
|
|
||||||
import org.hostsharing.hsadminng.security.SecurityUtils;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,7 +50,7 @@ public enum Role {
|
|||||||
*/
|
*/
|
||||||
FINANCIAL_CONTACT(22) {
|
FINANCIAL_CONTACT(22) {
|
||||||
@Override
|
@Override
|
||||||
boolean covers(final Role role) {
|
public boolean covers(final Role role) {
|
||||||
if (role == ACTUAL_CUSTOMER_USER) {
|
if (role == ACTUAL_CUSTOMER_USER) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -94,14 +92,15 @@ public enum Role {
|
|||||||
*
|
*
|
||||||
* Where 'this' means the Java instance itself as a role of a system user.
|
* Where 'this' means the Java instance itself as a role of a system user.
|
||||||
*
|
*
|
||||||
* @example
|
* {@code
|
||||||
* Role.HOSTMASTER.covers(Role.ANY_CUSTOMER_USER) == true
|
* Role.HOSTMASTER.covers(Role.ANY_CUSTOMER_USER) == true
|
||||||
|
* }
|
||||||
*
|
*
|
||||||
* @param role The required role for a resource.
|
* @param role The required role for a resource.
|
||||||
*
|
*
|
||||||
* @return whether this role comprises the given role
|
* @return whether this role comprises the given role
|
||||||
*/
|
*/
|
||||||
boolean covers(final Role role) {
|
public boolean covers(final Role role) {
|
||||||
return this == role || this.level < role.level;
|
return this == role || this.level < role.level;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,8 +130,6 @@ public enum Role {
|
|||||||
*/
|
*/
|
||||||
public boolean isAllowedToUpdate(final Field field) {
|
public boolean isAllowedToUpdate(final Field field) {
|
||||||
|
|
||||||
final Role loginUserRole = SecurityUtils.getCurrentUserLogin().map(u -> Role.valueOf(u.toUpperCase())).orElse(Role.ANYBODY);
|
|
||||||
|
|
||||||
final AccessFor accessFor = field.getAnnotation(AccessFor.class);
|
final AccessFor accessFor = field.getAnnotation(AccessFor.class);
|
||||||
if (accessFor == null) {
|
if (accessFor == null) {
|
||||||
return false;
|
return false;
|
||||||
@ -150,8 +147,6 @@ public enum Role {
|
|||||||
*/
|
*/
|
||||||
public boolean isAllowedToRead(final Field field) {
|
public boolean isAllowedToRead(final Field field) {
|
||||||
|
|
||||||
final Role loginUserRole = SecurityUtils.getCurrentUserLogin().map(u -> Role.valueOf(u.toUpperCase())).orElse(Role.ANYBODY);
|
|
||||||
|
|
||||||
final AccessFor accessFor = field.getAnnotation(AccessFor.class);
|
final AccessFor accessFor = field.getAnnotation(AccessFor.class);
|
||||||
if (accessFor == null) {
|
if (accessFor == null) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package org.hostsharing.hsadminng.service.dto;
|
package org.hostsharing.hsadminng.service.dto;
|
||||||
|
|
||||||
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.Role;
|
import org.hostsharing.hsadminng.service.accessfilter.Role;
|
||||||
|
import org.hostsharing.hsadminng.service.accessfilter.SelfId;
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import javax.validation.constraints.Size;
|
import javax.validation.constraints.Size;
|
||||||
@ -15,6 +17,7 @@ import java.util.function.Consumer;
|
|||||||
*/
|
*/
|
||||||
public class MembershipDTO implements Serializable {
|
public class MembershipDTO implements Serializable {
|
||||||
|
|
||||||
|
@SelfId
|
||||||
@AccessFor(read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
|
@AccessFor(read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@ -33,8 +36,8 @@ 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;
|
||||||
|
|
||||||
// TODO @AccessFor(init = Role.ADMIN, read = Role.SUPPORTER)
|
@ParentId(CustomerDTO.class)
|
||||||
// @AccessReference(CustomerDTO.class, Role...)
|
@AccessFor(init = Role.ADMIN, read = Role.SUPPORTER)
|
||||||
private Long customerId;
|
private Long customerId;
|
||||||
|
|
||||||
@AccessFor(read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
|
@AccessFor(read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
package org.hostsharing.hsadminng.service.util;
|
package org.hostsharing.hsadminng.service.util;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.TreeNode;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public class ReflectionUtil {
|
public class ReflectionUtil {
|
||||||
|
|
||||||
@ -26,10 +23,10 @@ public class ReflectionUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> Object getValue(T dto, Field field) {
|
public static <T> T getValue(final T dto, final Field field) {
|
||||||
try {
|
try {
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
return field.get(dto);
|
return (T) field.get(dto);
|
||||||
} catch (IllegalAccessException e) {
|
} catch (IllegalAccessException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package org.hostsharing.hsadminng.service.accessfilter;
|
package org.hostsharing.hsadminng.service.accessfilter;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeId;
|
|
||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
import com.fasterxml.jackson.core.ObjectCodec;
|
import com.fasterxml.jackson.core.ObjectCodec;
|
||||||
import com.fasterxml.jackson.core.TreeNode;
|
import com.fasterxml.jackson.core.TreeNode;
|
||||||
@ -18,7 +17,8 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenLoginUserWithRole;
|
import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenAuthenticatedUser;
|
||||||
|
import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenUserHavingRole;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
|
|
||||||
@SuppressWarnings("ALL")
|
@SuppressWarnings("ALL")
|
||||||
@ -38,7 +38,8 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() {
|
public void init() {
|
||||||
givenLoginUserWithRole(Role.ANY_CUSTOMER_USER);
|
givenAuthenticatedUser();
|
||||||
|
givenUserHavingRole(GivenDto.class, 1234L, Role.ACTUAL_CUSTOMER_USER);
|
||||||
|
|
||||||
given(jsonParser.getCodec()).willReturn(codec);
|
given(jsonParser.getCodec()).willReturn(codec);
|
||||||
}
|
}
|
||||||
@ -46,7 +47,9 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldDeserializeStringField() throws IOException {
|
public void shouldDeserializeStringField() throws IOException {
|
||||||
// given
|
// given
|
||||||
givenJSonTree(asJSon(ImmutablePair.of("openStringField", "String Value")));
|
givenJSonTree(asJSon(
|
||||||
|
ImmutablePair.of("id", 1234L),
|
||||||
|
ImmutablePair.of("openStringField", "String Value")));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize();
|
GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize();
|
||||||
@ -58,7 +61,9 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldDeserializeIntegerField() throws IOException {
|
public void shouldDeserializeIntegerField() throws IOException {
|
||||||
// given
|
// given
|
||||||
givenJSonTree(asJSon(ImmutablePair.of("openIntegerField", 1234)));
|
givenJSonTree(asJSon(
|
||||||
|
ImmutablePair.of("id", 1234L),
|
||||||
|
ImmutablePair.of("openIntegerField", 1234)));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize();
|
GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize();
|
||||||
@ -70,7 +75,9 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldDeserializeLongField() throws IOException {
|
public void shouldDeserializeLongField() throws IOException {
|
||||||
// given
|
// given
|
||||||
givenJSonTree(asJSon(ImmutablePair.of("openLongField", 1234L)));
|
givenJSonTree(asJSon(
|
||||||
|
ImmutablePair.of("id", 1234L),
|
||||||
|
ImmutablePair.of("openLongField", 1234L)));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize();
|
GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize();
|
||||||
@ -82,8 +89,11 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldDeserializeStringFieldIfRequiredRoleIsCoveredByUser() throws IOException {
|
public void shouldDeserializeStringFieldIfRequiredRoleIsCoveredByUser() throws IOException {
|
||||||
// given
|
// given
|
||||||
givenLoginUserWithRole(Role.FINANCIAL_CONTACT);
|
givenAuthenticatedUser();
|
||||||
givenJSonTree(asJSon(ImmutablePair.of("restrictedField", "Restricted String Value")));
|
givenUserHavingRole(GivenDto.class, 1234L, Role.FINANCIAL_CONTACT);
|
||||||
|
givenJSonTree(asJSon(
|
||||||
|
ImmutablePair.of("id", 1234L),
|
||||||
|
ImmutablePair.of("restrictedField", "Restricted String Value")));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize();
|
GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize();
|
||||||
@ -95,7 +105,8 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldInitializeFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
public void shouldInitializeFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
||||||
// given
|
// given
|
||||||
givenLoginUserWithRole(Role.ANY_CUSTOMER_USER);
|
givenAuthenticatedUser();
|
||||||
|
givenUserHavingRole(null, null, Role.ANY_CUSTOMER_USER);
|
||||||
givenJSonTree(asJSon(ImmutablePair.of("restrictedField", "Restricted String Value")));
|
givenJSonTree(asJSon(ImmutablePair.of("restrictedField", "Restricted String Value")));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@ -109,9 +120,41 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldUpdateFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
public void shouldNotCreateIfRoleRequiredByParentEntityIsNotCoveredByUser() throws IOException {
|
||||||
// given
|
// given
|
||||||
givenLoginUserWithRole(Role.ANY_CUSTOMER_USER);
|
givenAuthenticatedUser();
|
||||||
|
givenUserHavingRole(GivenDto.class, 9999L, Role.CONTRACTUAL_CONTACT);
|
||||||
|
givenJSonTree(asJSon(ImmutablePair.of("parentId", 1111L)));
|
||||||
|
|
||||||
|
// when
|
||||||
|
Throwable exception = catchThrowable(() -> new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenChildDto.class).deserialize());
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> {
|
||||||
|
assertThat(badRequestAlertException.getParam()).isEqualTo("GivenChildDto.parentId");
|
||||||
|
assertThat(badRequestAlertException.getErrorKey()).isEqualTo("referencingProhibited");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateIfRoleRequiredByReferencedEntityIsCoveredByUser() throws IOException {
|
||||||
|
// given
|
||||||
|
givenAuthenticatedUser();
|
||||||
|
givenUserHavingRole(GivenDto.class, 1111L, Role.CONTRACTUAL_CONTACT);
|
||||||
|
givenJSonTree(asJSon(ImmutablePair.of("parentId", 1111L)));
|
||||||
|
|
||||||
|
// when
|
||||||
|
final GivenChildDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenChildDto.class).deserialize();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(actualDto.parentId).isEqualTo(1111L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotUpdateFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
||||||
|
// given
|
||||||
|
givenAuthenticatedUser();
|
||||||
|
givenUserHavingRole(GivenDto.class, 1234L, Role.ANY_CUSTOMER_USER);
|
||||||
givenJSonTree(asJSon(
|
givenJSonTree(asJSon(
|
||||||
ImmutablePair.of("id", 1234L),
|
ImmutablePair.of("id", 1234L),
|
||||||
ImmutablePair.of("restrictedField", "Restricted String Value")));
|
ImmutablePair.of("restrictedField", "Restricted String Value")));
|
||||||
@ -183,6 +226,20 @@ public class JSonDeserializerWithAccessFilterUnitTest {
|
|||||||
Long openLongField;
|
Long openLongField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class GivenChildDto {
|
||||||
|
|
||||||
|
@SelfId
|
||||||
|
@AccessFor(read = Role.ANY_CUSTOMER_USER)
|
||||||
|
Long id;
|
||||||
|
|
||||||
|
@AccessFor(init = Role.CONTRACTUAL_CONTACT, update = Role.CONTRACTUAL_CONTACT, read = Role.ACTUAL_CUSTOMER_USER)
|
||||||
|
@ParentId(GivenDto.class)
|
||||||
|
Long parentId;
|
||||||
|
|
||||||
|
@AccessFor(init = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT}, update = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT})
|
||||||
|
String restrictedField;
|
||||||
|
}
|
||||||
|
|
||||||
public static class GivenDtoWithMultipleSelfId {
|
public static class GivenDtoWithMultipleSelfId {
|
||||||
|
|
||||||
@SelfId
|
@SelfId
|
||||||
|
@ -15,7 +15,6 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenLoginUserWithRole;
|
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
@ -31,7 +30,8 @@ public class JSonSerializerWithAccessFilterUnitTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() {
|
public void init() {
|
||||||
givenLoginUserWithRole(Role.ANY_CUSTOMER_USER);
|
MockSecurityContext.givenAuthenticatedUser();
|
||||||
|
MockSecurityContext.givenUserHavingRole(GivenCustomerDto.class, 888L, Role.ANY_CUSTOMER_USER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -47,7 +47,8 @@ public class JSonSerializerWithAccessFilterUnitTest {
|
|||||||
public void shouldSerializeRestrictedFieldIfRequiredRoleIsCoveredByUser() throws IOException {
|
public void shouldSerializeRestrictedFieldIfRequiredRoleIsCoveredByUser() throws IOException {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
givenLoginUserWithRole(Role.FINANCIAL_CONTACT);
|
MockSecurityContext.givenAuthenticatedUser();
|
||||||
|
MockSecurityContext.givenUserHavingRole(GivenCustomerDto.class, 888L, Role.FINANCIAL_CONTACT);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize();
|
new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize();
|
||||||
@ -60,7 +61,8 @@ public class JSonSerializerWithAccessFilterUnitTest {
|
|||||||
public void shouldNotSerializeRestrictedFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
public void shouldNotSerializeRestrictedFieldIfRequiredRoleIsNotCoveredByUser() throws IOException {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
givenLoginUserWithRole(Role.ANY_CUSTOMER_USER);
|
MockSecurityContext.givenAuthenticatedUser();
|
||||||
|
MockSecurityContext.givenUserHavingRole(GivenCustomerDto.class, 888L, Role.ANY_CUSTOMER_USER);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize();
|
new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize();
|
||||||
@ -92,6 +94,7 @@ public class JSonSerializerWithAccessFilterUnitTest {
|
|||||||
|
|
||||||
private GivenDto createSampleDto() {
|
private GivenDto createSampleDto() {
|
||||||
final GivenDto dto = new GivenDto();
|
final GivenDto dto = new GivenDto();
|
||||||
|
dto.customerId = 888L;
|
||||||
dto.restrictedField = RandomStringUtils.randomAlphabetic(10);
|
dto.restrictedField = RandomStringUtils.randomAlphabetic(10);
|
||||||
dto.openStringField = RandomStringUtils.randomAlphabetic(10);
|
dto.openStringField = RandomStringUtils.randomAlphabetic(10);
|
||||||
dto.openIntegerField = RandomUtils.nextInt();
|
dto.openIntegerField = RandomUtils.nextInt();
|
||||||
@ -99,7 +102,15 @@ public class JSonSerializerWithAccessFilterUnitTest {
|
|||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class GivenCustomerDto {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private static class GivenDto {
|
private static class GivenDto {
|
||||||
|
|
||||||
|
@ParentId(GivenCustomerDto.class)
|
||||||
|
Long customerId;
|
||||||
|
|
||||||
@AccessFor(read = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT})
|
@AccessFor(read = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT})
|
||||||
String restrictedField;
|
String restrictedField;
|
||||||
|
|
||||||
|
@ -5,20 +5,20 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
|||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
public class MockSecurityContext {
|
public class MockSecurityContext {
|
||||||
|
|
||||||
public static void givenLoginUserWithRole(final Role userRole) {
|
public static void givenAuthenticatedUser() {
|
||||||
final String fakeUserName = userRole.name();
|
|
||||||
|
|
||||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||||
securityContext.setAuthentication(new UsernamePasswordAuthenticationToken(fakeUserName, "dummy"));
|
securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("dummyUser", "dummyPassword"));
|
||||||
SecurityContextHolder.setContext(securityContext);
|
SecurityContextHolder.setContext(securityContext);
|
||||||
Optional<String> login = SecurityUtils.getCurrentUserLogin();
|
SecurityUtils.clearUserRoles();
|
||||||
|
|
||||||
assertThat(login).describedAs("precondition failed").contains(fakeUserName);
|
assertThat(SecurityUtils.getCurrentUserLogin()).describedAs("precondition failed").hasValue("dummyUser");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void givenUserHavingRole(final Class<?> onClass, final Long onId, final Role role) {
|
||||||
|
SecurityUtils.addUserRole(onClass, onId, role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,22 +2,18 @@ package org.hostsharing.hsadminng.service.dto;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.hostsharing.hsadminng.security.SecurityUtils;
|
|
||||||
import org.hostsharing.hsadminng.service.accessfilter.Role;
|
import org.hostsharing.hsadminng.service.accessfilter.Role;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.json.JsonTest;
|
import org.springframework.boot.test.autoconfigure.json.JsonTest;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenLoginUserWithRole;
|
import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenAuthenticatedUser;
|
||||||
|
import static org.hostsharing.hsadminng.service.accessfilter.MockSecurityContext.givenUserHavingRole;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
@JsonTest
|
@JsonTest
|
||||||
@ -31,7 +27,8 @@ public class CustomerDTOUnitTest {
|
|||||||
public void testSerializationAsContractualCustomerContact() throws JsonProcessingException {
|
public void testSerializationAsContractualCustomerContact() throws JsonProcessingException {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
givenLoginUserWithRole(Role.ANY_CUSTOMER_USER);
|
givenAuthenticatedUser();
|
||||||
|
givenUserHavingRole(CustomerDTO.class, null, Role.ANY_CUSTOMER_USER);
|
||||||
CustomerDTO given = createSomeCustomerDTO();
|
CustomerDTO given = createSomeCustomerDTO();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@ -50,7 +47,8 @@ public class CustomerDTOUnitTest {
|
|||||||
public void testSerializationAsSupporter() throws JsonProcessingException {
|
public void testSerializationAsSupporter() throws JsonProcessingException {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
givenLoginUserWithRole(Role.SUPPORTER);
|
givenAuthenticatedUser();
|
||||||
|
givenUserHavingRole(CustomerDTO.class, null, Role.SUPPORTER);
|
||||||
CustomerDTO given = createSomeCustomerDTO();
|
CustomerDTO given = createSomeCustomerDTO();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@ -63,7 +61,8 @@ public class CustomerDTOUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testDeserializeAsContractualCustomerContact() throws IOException {
|
public void testDeserializeAsContractualCustomerContact() throws IOException {
|
||||||
// given
|
// given
|
||||||
givenLoginUserWithRole(Role.CONTRACTUAL_CONTACT);
|
givenAuthenticatedUser();
|
||||||
|
givenUserHavingRole(CustomerDTO.class, null, Role.CONTRACTUAL_CONTACT);
|
||||||
String json = "{\"id\":1234,\"contractualSalutation\":\"Hallo Updated\",\"billingSalutation\":\"Moin Updated\"}";
|
String json = "{\"id\":1234,\"contractualSalutation\":\"Hallo Updated\",\"billingSalutation\":\"Moin Updated\"}";
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@ -77,6 +76,8 @@ public class CustomerDTOUnitTest {
|
|||||||
assertThat(actual).isEqualToComparingFieldByField(expected);
|
assertThat(actual).isEqualToComparingFieldByField(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- only test fixture below ---
|
||||||
|
|
||||||
private String createExpectedJSon(CustomerDTO dto) {
|
private String createExpectedJSon(CustomerDTO dto) {
|
||||||
String json = // the fields in alphanumeric order:
|
String json = // the fields in alphanumeric order:
|
||||||
toJSonFieldDefinitionIfPresent("id", dto.getId()) +
|
toJSonFieldDefinitionIfPresent("id", dto.getId()) +
|
||||||
|
Loading…
Reference in New Issue
Block a user