Deserializer: improved test code coverage for IGNORE field access

This commit is contained in:
Michael Hoennig 2019-05-16 20:25:02 +02:00
parent f9b68df901
commit e7cb3622f3
4 changed files with 106 additions and 46 deletions

View File

@ -82,13 +82,16 @@ abstract class JSonAccessFilter<T extends AccessMappings> {
final Class<?> parentDtoClass = ReflectionUtil.<T> determineGenericInterfaceParameter(parentDtoLoader, rawType, 0); final Class<?> parentDtoClass = ReflectionUtil.<T> determineGenericInterfaceParameter(parentDtoLoader, rawType, 0);
final Long parentId = ReflectionUtil.getValue(dto, parentIdField); final Long parentId = ReflectionUtil.getValue(dto, parentIdField);
if (parentId == null) {
return emptySet();
}
final Set<Role> rolesOnParent = getLoginUserDirectRolesFor(parentDtoClass, parentId); final Set<Role> rolesOnParent = getLoginUserDirectRolesFor(parentDtoClass, parentId);
final Object parentEntity = loadDto(parentDtoLoader, parentId); final Object parentEntity = loadDto(parentDtoLoader, parentId);
return union(rolesOnParent, getLoginUserRoleOnAncestorIfHigher(parentEntity)); return union(rolesOnParent, getLoginUserRoleOnAncestorIfHigher(parentEntity));
} }
private Set<Role> getLoginUserDirectRolesFor(final Class<?> dtoClass, final Long id) { private Set<Role> getLoginUserDirectRolesFor(final Class<?> dtoClass, final long id) {
if (!SecurityUtils.isAuthenticated()) { if (!SecurityUtils.isAuthenticated()) {
return emptySet(); return emptySet();
} }

View File

@ -199,11 +199,19 @@ public abstract class JsonDeserializerWithAccessFilter<T extends AccessMappings>
private void checkAccessToWrittenFields(final T currentDto) { private void checkAccessToWrittenFields(final T currentDto) {
updatingFields.forEach( updatingFields.forEach(
field -> { field -> {
// TODO this ugly code needs cleanup
if (!field.equals(selfIdField)) { if (!field.equals(selfIdField)) {
final Set<Role> roles = getLoginUserRoles(); final Set<Role> roles = getLoginUserRoles();
if (isInitAccess()) { if (isInitAccess()) {
if (!isAllowedToInit(roles, field)) { validateInitAccess(field, roles);
} else {
validateUpdateAccess(field, roles);
}
}
});
}
private void validateInitAccess(Field field, Set<Role> roles) {
if (!Role.toBeIgnoredForUpdates(field) && !isAllowedToInit(roles, field)) {
if (!field.equals(parentIdField)) { if (!field.equals(parentIdField)) {
throw new BadRequestAlertException( throw new BadRequestAlertException(
"Initialization of field " + toDisplay(field) "Initialization of field " + toDisplay(field)
@ -220,7 +228,10 @@ public abstract class JsonDeserializerWithAccessFilter<T extends AccessMappings>
"referencingProhibited"); "referencingProhibited");
} }
} }
} else if (!Role.toBeIgnoredForUpdates(field) && !isAllowedToUpdate(getLoginUserRoles(), field)) { }
private void validateUpdateAccess(Field field, Set<Role> roles) {
if (!Role.toBeIgnoredForUpdates(field) && !isAllowedToUpdate(getLoginUserRoles(), field)) {
throw new BadRequestAlertException( throw new BadRequestAlertException(
"Update of field " + toDisplay(field) + " prohibited for current user role(s): " "Update of field " + toDisplay(field) + " prohibited for current user role(s): "
+ Joiner.on("+").join(roles), + Joiner.on("+").join(roles),
@ -228,8 +239,6 @@ public abstract class JsonDeserializerWithAccessFilter<T extends AccessMappings>
"updateProhibited"); "updateProhibited");
} }
} }
});
}
private boolean isAllowedToInit(final Set<Role> roles, final Field field) { private boolean isAllowedToInit(final Set<Role> roles, final Field field) {
for (Role role : roles) { for (Role role : roles) {

View File

@ -106,6 +106,9 @@ public class JSonAccessFilterTestFixture {
@AccessFor(init = ANYBODY, update = ANYBODY, read = ANYBODY) @AccessFor(init = ANYBODY, update = ANYBODY, read = ANYBODY)
int[] openArrayField; int[] openArrayField;
@AccessFor(init = IGNORED, update = IGNORED, read = ANYBODY)
String displayLabel;
} }
static abstract class GivenService implements IdToDtoResolver<GivenDto> { static abstract class GivenService implements IdToDtoResolver<GivenDto> {

View File

@ -119,7 +119,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
ImmutablePair.of("openStringField", null))); ImmutablePair.of("openStringField", null)));
// when // when
final GivenDto actualDto = deserializerForGivenDto().deserialize(jsonParser, null); final GivenDto actualDto = deserializerFor(GivenDto.class).deserialize(jsonParser, null);
// then // then
assertThat(actualDto.openStringField).isNull(); assertThat(actualDto.openStringField).isNull();
@ -135,7 +135,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
ImmutablePair.of("openStringField", "String Value"))); ImmutablePair.of("openStringField", "String Value")));
// when // when
final GivenDto actualDto = deserializerForGivenDto().deserialize(jsonParser, null); final GivenDto actualDto = deserializerFor(GivenDto.class).deserialize(jsonParser, null);
// then // then
assertThat(actualDto.openStringField).isEqualTo("String Value"); assertThat(actualDto.openStringField).isEqualTo("String Value");
@ -152,7 +152,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
// when // when
// @formatter:off // @formatter:off
final GivenDto actualDto = deserializerForGivenDto().deserialize(jsonParser, null);; final GivenDto actualDto = deserializerFor(GivenDto.class).deserialize(jsonParser, null);;
// @formatter:on // @formatter:on
// then // then
@ -170,7 +170,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
ImmutablePair.of("restrictedBigDecimalField", SOME_BIG_DECIMAL_WITH_ANOTHER_SCALE))); ImmutablePair.of("restrictedBigDecimalField", SOME_BIG_DECIMAL_WITH_ANOTHER_SCALE)));
// when // when
final GivenDto actualDto = deserializerForGivenDto().deserialize(jsonParser, null); final GivenDto actualDto = deserializerFor(GivenDto.class).deserialize(jsonParser, null);
; ;
// then // then
@ -201,7 +201,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
ImmutablePair.of("openEnumField", TestEnum.GREEN))); ImmutablePair.of("openEnumField", TestEnum.GREEN)));
// when // when
final GivenDto actualDto = deserializerForGivenDto().deserialize(jsonParser, null); final GivenDto actualDto = deserializerFor(GivenDto.class).deserialize(jsonParser, null);
; ;
// then // then
@ -227,7 +227,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
ImmutablePair.of("openArrayField", Arrays.asList(11, 22, 33)))); ImmutablePair.of("openArrayField", Arrays.asList(11, 22, 33))));
// when // when
Throwable exception = catchThrowable(() -> deserializerForGivenDto().deserialize(jsonParser, null)); Throwable exception = catchThrowable(() -> deserializerFor(GivenDto.class).deserialize(jsonParser, null));
// then // then
assertThat(exception).isInstanceOf(NotImplementedException.class); assertThat(exception).isInstanceOf(NotImplementedException.class);
@ -245,7 +245,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
ImmutablePair.of("restrictedField", "update value of restricted field"))); ImmutablePair.of("restrictedField", "update value of restricted field")));
// when // when
final GivenDto actualDto = deserializerForGivenDto().deserialize(jsonParser, null); final GivenDto actualDto = deserializerFor(GivenDto.class).deserialize(jsonParser, null);
// then // then
assertThat(actualDto.restrictedField).isEqualTo("update value of restricted field"); assertThat(actualDto.restrictedField).isEqualTo("update value of restricted field");
@ -263,7 +263,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
ImmutablePair.of("restrictedField", "initial value of restricted field"))); ImmutablePair.of("restrictedField", "initial value of restricted field")));
// when // when
final GivenDto actualDto = deserializerForGivenDto().deserialize(jsonParser, null); final GivenDto actualDto = deserializerFor(GivenDto.class).deserialize(jsonParser, null);
// then // then
assertThat(actualDto.restrictedField).isEqualTo("initial value of restricted field"); assertThat(actualDto.restrictedField).isEqualTo("initial value of restricted field");
@ -280,7 +280,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
ImmutablePair.of("restrictedField", "updated value of restricted field"))); ImmutablePair.of("restrictedField", "updated value of restricted field")));
// when // when
final Throwable exception = catchThrowable(() -> deserializerForGivenDto().deserialize(jsonParser, null)); final Throwable exception = catchThrowable(() -> deserializerFor(GivenDto.class).deserialize(jsonParser, null));
// then // then
assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> { assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> {
@ -300,7 +300,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
ImmutablePair.of("restrictedField", "another value of restricted field"))); ImmutablePair.of("restrictedField", "another value of restricted field")));
// when // when
final Throwable exception = catchThrowable(() -> deserializerForGivenDto().deserialize(jsonParser, null)); final Throwable exception = catchThrowable(() -> deserializerFor(GivenDto.class).deserialize(jsonParser, null));
// then // then
assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> { assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> {
@ -320,7 +320,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
// when // when
Throwable exception = catchThrowable( Throwable exception = catchThrowable(
() -> deserializerForGivenChildDto().deserialize(jsonParser, null)); () -> deserializerFor(GivenChildDto.class).deserialize(jsonParser, null));
// then // then
assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> { assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> {
@ -339,7 +339,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
ImmutablePair.of("parentId", 1234L))); ImmutablePair.of("parentId", 1234L)));
// when // when
final GivenChildDto actualDto = deserializerForGivenChildDto().deserialize(jsonParser, null); final GivenChildDto actualDto = deserializerFor(GivenChildDto.class).deserialize(jsonParser, null);
; ;
// then // then
@ -359,7 +359,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
// when // when
final Throwable exception = catchThrowable( final Throwable exception = catchThrowable(
() -> deserializerForGivenDto().deserialize(jsonParser, null)); () -> deserializerFor(GivenDto.class).deserialize(jsonParser, null));
// then // then
assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> { assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, badRequestAlertException -> {
@ -375,7 +375,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
// when // when
final Throwable exception = catchThrowable( final Throwable exception = catchThrowable(
() -> deserializerForGivenDtoWithMultipleSelfId().deserialize(jsonParser, null)); () -> deserializerFor(GivenDtoWithMultipleSelfId.class).deserialize(jsonParser, null));
// then // then
assertThat(exception).isInstanceOf(AssertionError.class) assertThat(exception).isInstanceOf(AssertionError.class)
@ -390,7 +390,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
// when // when
final Throwable exception = catchThrowable( final Throwable exception = catchThrowable(
() -> deserializerForGivenDtoWithUnknownFieldType().deserialize(jsonParser, null)); () -> deserializerFor(GivenDtoWithUnknownFieldType.class).deserialize(jsonParser, null));
// then // then
assertThat(exception).isInstanceOf(NotImplementedException.class) assertThat(exception).isInstanceOf(NotImplementedException.class)
@ -407,7 +407,7 @@ public class JSonDeserializationWithAccessFilterUnitTest {
// when // when
final Throwable exception = catchThrowable( final Throwable exception = catchThrowable(
() -> deserializerForGivenDto().deserialize(jsonParser, null)); () -> deserializerFor(GivenDto.class).deserialize(jsonParser, null));
// then // then
assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, (exc) -> { assertThat(exception).isInstanceOfSatisfying(BadRequestAlertException.class, (exc) -> {
@ -417,6 +417,38 @@ public class JSonDeserializationWithAccessFilterUnitTest {
}); });
} }
@Test
public void shouldIgnorePropertyToIgnoreForInit() throws IOException {
// given
securityContext.havingAuthenticatedUser().withAuthority(AuthoritiesConstants.ADMIN);
givenJSonTree(
asJSon(
ImmutablePair.of("displayLabel", "Some Value")));
// when
deserializerFor(GivenDto.class).deserialize(jsonParser, null);
final Throwable exception = catchThrowable(() -> deserializerFor(GivenDto.class).deserialize(jsonParser, null));
// then
assertThat(exception).isNull();
}
@Test
public void shouldIgnorePropertyToIgnoreForUpdate() throws IOException {
// given
securityContext.havingAuthenticatedUser().withAuthority(AuthoritiesConstants.ADMIN);
givenJSonTree(
asJSon(
ImmutablePair.of("id", 1234L),
ImmutablePair.of("displayLabel", "Some Value")));
// when
final Throwable exception = catchThrowable(() -> deserializerFor(GivenDto.class).deserialize(jsonParser, null));
// then
assertThat(exception).isNull();
}
// --- only fixture code below --- // --- only fixture code below ---
private void givenJSonTree(String givenJSon) throws IOException { private void givenJSonTree(String givenJSon) throws IOException {
@ -425,28 +457,41 @@ public class JSonDeserializationWithAccessFilterUnitTest {
given(codec.readTree(jsonParser)).willReturn(new ObjectMapper().readTree(givenJSon)); given(codec.readTree(jsonParser)).willReturn(new ObjectMapper().readTree(givenJSon));
} }
// We need specialied factories for the deserializer subclasses so that the generic type can be accessed via reflection. // We need specialied factories for the deserializer subclasses
// so that the generic type can be accessed via reflection.
// And it's down here to keep the ugly formatting out of the test cases. // And it's down here to keep the ugly formatting out of the test cases.
// The trick with the unused ...-parameterr might look strange but the reason is
// that I wanted the concrete class to be navigable in the tests and
// multiple deserializer(Class<...Dto>) methods would have the same erasure
// and adding the type the method name, is redundant for the reader.
public JsonDeserializerWithAccessFilter<GivenDto> deserializerForGivenDto() throws IOException { public JsonDeserializerWithAccessFilter<GivenDto> deserializerFor(
final Class<GivenDto> clazz,
final GivenDto... qualifier) {
return new JsonDeserializerWithAccessFilter<GivenDto>(ctx, userRoleAssignmentService) { return new JsonDeserializerWithAccessFilter<GivenDto>(ctx, userRoleAssignmentService) {
// no need to overload any method here // no need to overload any method here
}; };
} }
public JsonDeserializerWithAccessFilter<GivenChildDto> deserializerForGivenChildDto() throws IOException { public JsonDeserializerWithAccessFilter<GivenChildDto> deserializerFor(
final Class<GivenChildDto> clazz,
final GivenChildDto... qualifier) {
return new JsonDeserializerWithAccessFilter<GivenChildDto>(ctx, userRoleAssignmentService) { return new JsonDeserializerWithAccessFilter<GivenChildDto>(ctx, userRoleAssignmentService) {
// no need to overload any method here // no need to overload any method here
}; };
} }
private JsonDeserializer<GivenDtoWithMultipleSelfId> deserializerForGivenDtoWithMultipleSelfId() { private JsonDeserializer<GivenDtoWithMultipleSelfId> deserializerFor(
final Class<GivenDtoWithMultipleSelfId> clazz,
final GivenDtoWithMultipleSelfId... qualifier) {
return new JsonDeserializerWithAccessFilter<GivenDtoWithMultipleSelfId>(ctx, userRoleAssignmentService) { return new JsonDeserializerWithAccessFilter<GivenDtoWithMultipleSelfId>(ctx, userRoleAssignmentService) {
// no need to overload any method here // no need to overload any method here
}; };
} }
private JsonDeserializer<GivenDtoWithUnknownFieldType> deserializerForGivenDtoWithUnknownFieldType() { private JsonDeserializer<GivenDtoWithUnknownFieldType> deserializerFor(
final Class<GivenDtoWithUnknownFieldType> clazz,
final GivenDtoWithUnknownFieldType... qualifier) {
return new JsonDeserializerWithAccessFilter<GivenDtoWithUnknownFieldType>(ctx, userRoleAssignmentService) { return new JsonDeserializerWithAccessFilter<GivenDtoWithUnknownFieldType>(ctx, userRoleAssignmentService) {
// no need to overload any method here // no need to overload any method here
}; };