From a94516b3ceed2a05f61779538ae025475064363d Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 23 Apr 2019 14:37:49 +0200 Subject: [PATCH] JSonSerializer/DeserializerWithAccessFilter: also use role in parent --- .../hsadminng/security/SecurityUtils.java | 43 ++++++++++ .../accessfilter/JSonAccessFilter.java | 65 +++++++++++++++ .../JSonDeserializerWithAccessFilter.java | 44 ++--------- .../JSonSerializerWithAccessFilter.java | 13 +-- .../service/accessfilter/ParentId.java | 17 ++++ .../hsadminng/service/accessfilter/Role.java | 13 +-- .../hsadminng/service/dto/MembershipDTO.java | 7 +- .../service/util/ReflectionUtil.java | 7 +- ...nDeserializerWithAccessFilterUnitTest.java | 79 ++++++++++++++++--- ...SonSerializerWithAccessFilterUnitTest.java | 19 ++++- .../accessfilter/MockSecurityContext.java | 16 ++-- .../service/dto/CustomerDTOUnitTest.java | 19 ++--- 12 files changed, 247 insertions(+), 95 deletions(-) create mode 100644 src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java create mode 100644 src/main/java/org/hostsharing/hsadminng/service/accessfilter/ParentId.java diff --git a/src/main/java/org/hostsharing/hsadminng/security/SecurityUtils.java b/src/main/java/org/hostsharing/hsadminng/security/SecurityUtils.java index eb655672..e06fe195 100644 --- a/src/main/java/org/hostsharing/hsadminng/security/SecurityUtils.java +++ b/src/main/java/org/hostsharing/hsadminng/security/SecurityUtils.java @@ -1,9 +1,12 @@ 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.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; /** @@ -11,6 +14,8 @@ import java.util.Optional; */ public final class SecurityUtils { + private static List userRoleAssignments = new ArrayList<>(); + private SecurityUtils() { } @@ -73,4 +78,42 @@ public final class SecurityUtils { .anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority))) .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; + } + } } diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java new file mode 100644 index 00000000..c6faaf04 --- /dev/null +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java @@ -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 { + 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) ); + } +} diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java index dc9f2f62..9dc104d1 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilter.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.databind.node.IntNode; import com.fasterxml.jackson.databind.node.LongNode; import com.fasterxml.jackson.databind.node.TextNode; import org.apache.commons.lang3.NotImplementedException; -import org.hostsharing.hsadminng.security.SecurityUtils; import org.hostsharing.hsadminng.service.util.ReflectionUtil; 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; -public class JSonDeserializerWithAccessFilter { +public class JSonDeserializerWithAccessFilter extends JSonAccessFilter { - private final T dto; private final TreeNode treeNode; private final Set modifiedFields = new HashSet<>(); - private Field selfIdField = null; public JSonDeserializerWithAccessFilter(final JsonParser jsonParser, final DeserializationContext deserializationContext, Class dtoClass) { + super(unchecked(dtoClass::newInstance)); this.treeNode = unchecked(() -> jsonParser.getCodec().readTree(jsonParser)); - this.dto = unchecked(dtoClass::newInstance); } // Jackson deserializes from the JsonParser, thus no input parameter needed. public T deserialize() { - determineSelfIdField(); deserializeValues(); checkAccessToModifiedFields(); 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() { treeNode.fieldNames().forEachRemaining(fieldName -> { try { @@ -90,34 +75,21 @@ public class JSonDeserializerWithAccessFilter { modifiedFields.add(field); } - private Object getId() { - if (selfIdField == null) { - return null; - } - return ReflectionUtil.getValue(dto, selfIdField); - } - private void checkAccessToModifiedFields() { modifiedFields.forEach(field -> { if ( !field.equals(selfIdField) ) { if (getId() == null) { 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) { - if (!getLoginUserRole().isAllowedToUpdate(field)) { + } else if (!getLoginUserRole().isAllowedToUpdate(field)) { 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); - } } diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java index c4d1986a..b2ec0c57 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilter.java @@ -2,28 +2,22 @@ package org.hostsharing.hsadminng.service.accessfilter; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import org.apache.commons.lang3.NotImplementedException; -import org.hostsharing.hsadminng.security.SecurityUtils; -import org.springframework.boot.jackson.JsonComponent; import java.io.IOException; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -public class JSonSerializerWithAccessFilter { +public class JSonSerializerWithAccessFilter extends JSonAccessFilter { private final JsonGenerator jsonGenerator; private final SerializerProvider serializerProvider; - private final T dto; public JSonSerializerWithAccessFilter(final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider, final T dto) { + super(dto); this.jsonGenerator = jsonGenerator; this.serializerProvider = serializerProvider; - this.dto = dto; } // Jackson serializes into the JsonGenerator, thus no return value needed. @@ -65,7 +59,4 @@ public class JSonSerializerWithAccessFilter { } } - private Role getLoginUserRole() { - return SecurityUtils.getCurrentUserLogin().map(u -> Role.valueOf(u.toUpperCase())).orElse(Role.ANYBODY); - } } diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/ParentId.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/ParentId.java new file mode 100644 index 00000000..51ddc0a2 --- /dev/null +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/ParentId.java @@ -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(); +} diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/Role.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/Role.java index 81ff1078..83f667d0 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/Role.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/Role.java @@ -1,7 +1,5 @@ package org.hostsharing.hsadminng.service.accessfilter; -import org.hostsharing.hsadminng.security.SecurityUtils; - import java.lang.reflect.Field; /** @@ -52,7 +50,7 @@ public enum Role { */ FINANCIAL_CONTACT(22) { @Override - boolean covers(final Role role) { + public boolean covers(final Role role) { if (role == ACTUAL_CUSTOMER_USER) { return false; } @@ -94,14 +92,15 @@ public enum Role { * * Where 'this' means the Java instance itself as a role of a system user. * - * @example + * {@code * Role.HOSTMASTER.covers(Role.ANY_CUSTOMER_USER) == true + * } * * @param role The required role for a resource. * * @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; } @@ -131,8 +130,6 @@ public enum Role { */ 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); if (accessFor == null) { return false; @@ -150,8 +147,6 @@ public enum Role { */ 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); if (accessFor == null) { return false; diff --git a/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java b/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java index 0f4b5dfc..15130c3c 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java @@ -1,7 +1,9 @@ package org.hostsharing.hsadminng.service.dto; 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.SelfId; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @@ -15,6 +17,7 @@ import java.util.function.Consumer; */ public class MembershipDTO implements Serializable { + @SelfId @AccessFor(read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) private Long id; @@ -33,8 +36,8 @@ public class MembershipDTO implements Serializable { @AccessFor(init = Role.ADMIN, read = Role.SUPPORTER) private String remark; - // TODO @AccessFor(init = Role.ADMIN, read = Role.SUPPORTER) - // @AccessReference(CustomerDTO.class, Role...) + @ParentId(CustomerDTO.class) + @AccessFor(init = Role.ADMIN, read = Role.SUPPORTER) private Long customerId; @AccessFor(read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT}) diff --git a/src/main/java/org/hostsharing/hsadminng/service/util/ReflectionUtil.java b/src/main/java/org/hostsharing/hsadminng/service/util/ReflectionUtil.java index 0bcabc78..377f2748 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/util/ReflectionUtil.java +++ b/src/main/java/org/hostsharing/hsadminng/service/util/ReflectionUtil.java @@ -1,9 +1,6 @@ package org.hostsharing.hsadminng.service.util; -import com.fasterxml.jackson.core.TreeNode; - import java.lang.reflect.Field; -import java.util.function.Supplier; public class ReflectionUtil { @@ -26,10 +23,10 @@ public class ReflectionUtil { } } - public static Object getValue(T dto, Field field) { + public static T getValue(final T dto, final Field field) { try { field.setAccessible(true); - return field.get(dto); + return (T) field.get(dto); } catch (IllegalAccessException e) { throw new RuntimeException(e); } diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java index 9ddafa09..ed3fa7fb 100644 --- a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java +++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonDeserializerWithAccessFilterUnitTest.java @@ -1,6 +1,5 @@ package org.hostsharing.hsadminng.service.accessfilter; -import com.fasterxml.jackson.annotation.JsonTypeId; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.ObjectCodec; 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.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; @SuppressWarnings("ALL") @@ -38,7 +38,8 @@ public class JSonDeserializerWithAccessFilterUnitTest { @Before public void init() { - givenLoginUserWithRole(Role.ANY_CUSTOMER_USER); + givenAuthenticatedUser(); + givenUserHavingRole(GivenDto.class, 1234L, Role.ACTUAL_CUSTOMER_USER); given(jsonParser.getCodec()).willReturn(codec); } @@ -46,7 +47,9 @@ public class JSonDeserializerWithAccessFilterUnitTest { @Test public void shouldDeserializeStringField() throws IOException { // given - givenJSonTree(asJSon(ImmutablePair.of("openStringField", "String Value"))); + givenJSonTree(asJSon( + ImmutablePair.of("id", 1234L), + ImmutablePair.of("openStringField", "String Value"))); // when GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize(); @@ -58,7 +61,9 @@ public class JSonDeserializerWithAccessFilterUnitTest { @Test public void shouldDeserializeIntegerField() throws IOException { // given - givenJSonTree(asJSon(ImmutablePair.of("openIntegerField", 1234))); + givenJSonTree(asJSon( + ImmutablePair.of("id", 1234L), + ImmutablePair.of("openIntegerField", 1234))); // when GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize(); @@ -70,7 +75,9 @@ public class JSonDeserializerWithAccessFilterUnitTest { @Test public void shouldDeserializeLongField() throws IOException { // given - givenJSonTree(asJSon(ImmutablePair.of("openLongField", 1234L))); + givenJSonTree(asJSon( + ImmutablePair.of("id", 1234L), + ImmutablePair.of("openLongField", 1234L))); // when GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize(); @@ -82,8 +89,11 @@ public class JSonDeserializerWithAccessFilterUnitTest { @Test public void shouldDeserializeStringFieldIfRequiredRoleIsCoveredByUser() throws IOException { // given - givenLoginUserWithRole(Role.FINANCIAL_CONTACT); - givenJSonTree(asJSon(ImmutablePair.of("restrictedField", "Restricted String Value"))); + givenAuthenticatedUser(); + givenUserHavingRole(GivenDto.class, 1234L, Role.FINANCIAL_CONTACT); + givenJSonTree(asJSon( + ImmutablePair.of("id", 1234L), + ImmutablePair.of("restrictedField", "Restricted String Value"))); // when GivenDto actualDto = new JSonDeserializerWithAccessFilter<>(jsonParser, null, GivenDto.class).deserialize(); @@ -95,7 +105,8 @@ public class JSonDeserializerWithAccessFilterUnitTest { @Test public void shouldInitializeFieldIfRequiredRoleIsNotCoveredByUser() throws IOException { // given - givenLoginUserWithRole(Role.ANY_CUSTOMER_USER); + givenAuthenticatedUser(); + givenUserHavingRole(null, null, Role.ANY_CUSTOMER_USER); givenJSonTree(asJSon(ImmutablePair.of("restrictedField", "Restricted String Value"))); // when @@ -109,9 +120,41 @@ public class JSonDeserializerWithAccessFilterUnitTest { } @Test - public void shouldUpdateFieldIfRequiredRoleIsNotCoveredByUser() throws IOException { + public void shouldNotCreateIfRoleRequiredByParentEntityIsNotCoveredByUser() throws IOException { // 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( ImmutablePair.of("id", 1234L), ImmutablePair.of("restrictedField", "Restricted String Value"))); @@ -183,6 +226,20 @@ public class JSonDeserializerWithAccessFilterUnitTest { 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 { @SelfId diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java index 432a53ad..7d5265fc 100644 --- a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java +++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonSerializerWithAccessFilterUnitTest.java @@ -15,7 +15,6 @@ import java.io.IOException; import static org.assertj.core.api.Assertions.assertThat; 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.verify; @@ -31,7 +30,8 @@ public class JSonSerializerWithAccessFilterUnitTest { @Before public void init() { - givenLoginUserWithRole(Role.ANY_CUSTOMER_USER); + MockSecurityContext.givenAuthenticatedUser(); + MockSecurityContext.givenUserHavingRole(GivenCustomerDto.class, 888L, Role.ANY_CUSTOMER_USER); } @Test @@ -47,7 +47,8 @@ public class JSonSerializerWithAccessFilterUnitTest { public void shouldSerializeRestrictedFieldIfRequiredRoleIsCoveredByUser() throws IOException { // given - givenLoginUserWithRole(Role.FINANCIAL_CONTACT); + MockSecurityContext.givenAuthenticatedUser(); + MockSecurityContext.givenUserHavingRole(GivenCustomerDto.class, 888L, Role.FINANCIAL_CONTACT); // when new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize(); @@ -60,7 +61,8 @@ public class JSonSerializerWithAccessFilterUnitTest { public void shouldNotSerializeRestrictedFieldIfRequiredRoleIsNotCoveredByUser() throws IOException { // given - givenLoginUserWithRole(Role.ANY_CUSTOMER_USER); + MockSecurityContext.givenAuthenticatedUser(); + MockSecurityContext.givenUserHavingRole(GivenCustomerDto.class, 888L, Role.ANY_CUSTOMER_USER); // when new JSonSerializerWithAccessFilter<>(jsonGenerator, null, givenDTO).serialize(); @@ -92,6 +94,7 @@ public class JSonSerializerWithAccessFilterUnitTest { private GivenDto createSampleDto() { final GivenDto dto = new GivenDto(); + dto.customerId = 888L; dto.restrictedField = RandomStringUtils.randomAlphabetic(10); dto.openStringField = RandomStringUtils.randomAlphabetic(10); dto.openIntegerField = RandomUtils.nextInt(); @@ -99,7 +102,15 @@ public class JSonSerializerWithAccessFilterUnitTest { return dto; } + private static class GivenCustomerDto { + + } + private static class GivenDto { + + @ParentId(GivenCustomerDto.class) + Long customerId; + @AccessFor(read = {Role.TECHNICAL_CONTACT, Role.FINANCIAL_CONTACT}) String restrictedField; diff --git a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/MockSecurityContext.java b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/MockSecurityContext.java index 5a656c77..5b3f78b2 100644 --- a/src/test/java/org/hostsharing/hsadminng/service/accessfilter/MockSecurityContext.java +++ b/src/test/java/org/hostsharing/hsadminng/service/accessfilter/MockSecurityContext.java @@ -5,20 +5,20 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -import java.util.Optional; - import static org.assertj.core.api.Assertions.assertThat; public class MockSecurityContext { - public static void givenLoginUserWithRole(final Role userRole) { - final String fakeUserName = userRole.name(); - + public static void givenAuthenticatedUser() { SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(new UsernamePasswordAuthenticationToken(fakeUserName, "dummy")); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("dummyUser", "dummyPassword")); SecurityContextHolder.setContext(securityContext); - Optional 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); } } diff --git a/src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java b/src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java index 1379f574..34a016ae 100644 --- a/src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java +++ b/src/test/java/org/hostsharing/hsadminng/service/dto/CustomerDTOUnitTest.java @@ -2,22 +2,18 @@ package org.hostsharing.hsadminng.service.dto; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import org.hostsharing.hsadminng.security.SecurityUtils; import org.hostsharing.hsadminng.service.accessfilter.Role; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; 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 java.io.IOException; -import java.util.Optional; 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; @JsonTest @@ -31,7 +27,8 @@ public class CustomerDTOUnitTest { public void testSerializationAsContractualCustomerContact() throws JsonProcessingException { // given - givenLoginUserWithRole(Role.ANY_CUSTOMER_USER); + givenAuthenticatedUser(); + givenUserHavingRole(CustomerDTO.class, null, Role.ANY_CUSTOMER_USER); CustomerDTO given = createSomeCustomerDTO(); // when @@ -50,7 +47,8 @@ public class CustomerDTOUnitTest { public void testSerializationAsSupporter() throws JsonProcessingException { // given - givenLoginUserWithRole(Role.SUPPORTER); + givenAuthenticatedUser(); + givenUserHavingRole(CustomerDTO.class, null, Role.SUPPORTER); CustomerDTO given = createSomeCustomerDTO(); // when @@ -63,7 +61,8 @@ public class CustomerDTOUnitTest { @Test public void testDeserializeAsContractualCustomerContact() throws IOException { // given - givenLoginUserWithRole(Role.CONTRACTUAL_CONTACT); + givenAuthenticatedUser(); + givenUserHavingRole(CustomerDTO.class, null, Role.CONTRACTUAL_CONTACT); String json = "{\"id\":1234,\"contractualSalutation\":\"Hallo Updated\",\"billingSalutation\":\"Moin Updated\"}"; // when @@ -77,6 +76,8 @@ public class CustomerDTOUnitTest { assertThat(actual).isEqualToComparingFieldByField(expected); } + // --- only test fixture below --- + private String createExpectedJSon(CustomerDTO dto) { String json = // the fields in alphanumeric order: toJSonFieldDefinitionIfPresent("id", dto.getId()) +