JSonSerializer/DeserializerWithAccessFilter: also use role in parent

This commit is contained in:
Michael Hoennig 2019-04-23 14:37:49 +02:00
parent 1505e7bd66
commit a94516b3ce
12 changed files with 247 additions and 95 deletions

View File

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

View File

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

View File

@ -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)) {
if ( !field.equals(parentIdField)) {
throw new BadRequestAlertException("Initialization of field prohibited for current user", toDisplay(field), "initializationProhibited"); 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) { }
if (!getLoginUserRole().isAllowedToUpdate(field)) { } else 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);
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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()) +