From 7db2c23de1581b9781b6dc15ec26e8a57ebc213d Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Thu, 27 Jun 2019 23:48:16 +0200 Subject: [PATCH] #145 [Rights-Module] preparation for module specific roles --- .../hsadminng/domain/UserRoleAssignment.java | 60 +-- .../service/accessfilter/AccessFor.java | 8 +- .../accessfilter/JSonAccessFilter.java | 37 +- .../JsonDeserializerWithAccessFilter.java | 53 +-- .../JsonSerializerWithAccessFilter.java | 8 +- .../hsadminng/service/accessfilter/Role.java | 376 ++++++++++++------ .../hsadminng/service/dto/AssetDTO.java | 23 +- .../hsadminng/service/dto/CustomerDTO.java | 70 ++-- .../hsadminng/service/dto/MembershipDTO.java | 35 +- .../hsadminng/service/dto/SepaMandateDTO.java | 55 ++- .../hsadminng/service/dto/ShareDTO.java | 23 +- .../dto/UserRoleAssignmentCriteria.java | 4 +- .../service/util/ReflectionUtil.java | 56 +++ .../user-role-assignment.component.html | 2 +- .../UserRoleAssignmentServiceUnitTest.java | 26 +- .../accessfilter/JSonAccessFilterTest.java | 33 ++ .../JSonAccessFilterTestFixture.java | 91 +++-- .../service/accessfilter/JSonBuilder.java | 14 +- ...serializationWithAccessFilterUnitTest.java | 45 +-- ...SerializationWithAccessFilterUnitTest.java | 29 +- .../service/accessfilter/RoleUnitTest.java | 179 ++++----- .../accessfilter/SecurityContextFake.java | 5 + .../dto/AccessMappingsUnitTestBase.java | 21 +- .../service/dto/AssetDTOIntTest.java | 27 +- .../service/dto/AssetDTOUnitTest.java | 37 +- .../service/dto/CustomerDTOUnitTest.java | 22 +- .../service/dto/MembershipDTOIntTest.java | 30 +- .../service/dto/MembershipDTOUnitTest.java | 39 +- .../service/dto/SepaMandateDTOIntTest.java | 29 +- .../service/dto/SepaMandateDTOUnitTest.java | 37 +- .../service/dto/ShareDTOIntTest.java | 32 +- .../service/dto/ShareDTOUnitTest.java | 38 +- .../dto/UserRoleAssignmentUnitTest.java | 45 ++- .../web/rest/AssetResourceIntTest.java | 18 +- .../web/rest/ShareResourceIntTest.java | 18 +- .../UserRoleAssignmentResourceIntTest.java | 54 +-- 36 files changed, 945 insertions(+), 734 deletions(-) create mode 100644 src/test/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilterTest.java diff --git a/src/main/java/org/hostsharing/hsadminng/domain/UserRoleAssignment.java b/src/main/java/org/hostsharing/hsadminng/domain/UserRoleAssignment.java index a6174126..2351df19 100644 --- a/src/main/java/org/hostsharing/hsadminng/domain/UserRoleAssignment.java +++ b/src/main/java/org/hostsharing/hsadminng/domain/UserRoleAssignment.java @@ -1,22 +1,25 @@ // Licensed under Apache-2.0 package org.hostsharing.hsadminng.domain; -import org.hostsharing.hsadminng.repository.UserRepository; -import org.hostsharing.hsadminng.service.UserRoleAssignmentService; -import org.hostsharing.hsadminng.service.accessfilter.*; - +import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.TreeNode; - +import org.hostsharing.hsadminng.repository.UserRepository; +import org.hostsharing.hsadminng.service.UserRoleAssignmentService; +import org.hostsharing.hsadminng.service.accessfilter.*; +import org.hostsharing.hsadminng.service.accessfilter.Role.Admin; +import org.hostsharing.hsadminng.service.accessfilter.Role.Supporter; import org.springframework.boot.jackson.JsonComponent; import org.springframework.context.ApplicationContext; +import javax.persistence.*; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.lang.reflect.Field; import java.util.Objects; -import javax.persistence.*; -import javax.validation.constraints.*; +import static org.hostsharing.hsadminng.service.util.ReflectionUtil.of; /** * A UserRoleAssignment. @@ -24,41 +27,44 @@ import javax.validation.constraints.*; @Entity @Table(name = "user_role_assignment") @EntityTypeId(UserRoleAssignment.ENTITY_TYPE_ID) +@JsonAutoDetect( + fieldVisibility = JsonAutoDetect.Visibility.ANY, + getterVisibility = JsonAutoDetect.Visibility.NONE, + setterVisibility = JsonAutoDetect.Visibility.NONE) public class UserRoleAssignment implements AccessMappings { private static final long serialVersionUID = 1L; - public static final String ENTITY_TYPE_ID = "rights.UserRoleAssignment"; + private static final String USER_FIELD_NAME = "user"; - static final String USER_FIELD_NAME = "user"; + public static final String ENTITY_TYPE_ID = "rights.UserRoleAssignment"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator") @SequenceGenerator(name = "sequenceGenerator") @SelfId(resolver = UserRoleAssignmentService.class) - @AccessFor(read = Role.SUPPORTER) + @AccessFor(read = Supporter.class) private Long id; @NotNull @Size(max = 32) @Column(name = "entity_type_id", length = 32, nullable = false) - @AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = Role.SUPPORTER) + @AccessFor(init = Admin.class, update = Admin.class, read = Supporter.class) private String entityTypeId; @NotNull @Column(name = "entity_object_id", nullable = false) - @AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = Role.SUPPORTER) + @AccessFor(init = Admin.class, update = Admin.class, read = Supporter.class) private Long entityObjectId; @NotNull - @Enumerated(EnumType.STRING) @Column(name = "assigned_role", nullable = false) - @AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = Role.SUPPORTER) - private Role assignedRole; + @AccessFor(init = Admin.class, update = Admin.class, read = Supporter.class) + private String assignedRole; @ManyToOne @JsonIgnoreProperties("requireds") - @AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = Role.SUPPORTER) + @AccessFor(init = Admin.class, update = Admin.class, read = Supporter.class) private User user; // jhipster-needle-entity-add-field - JHipster will add fields here, do not remove @@ -103,16 +109,16 @@ public class UserRoleAssignment implements AccessMappings { } public Role getAssignedRole() { - return assignedRole; + return assignedRole != null ? Role.of(assignedRole) : null; } public UserRoleAssignment assignedRole(Role assignedRole) { - this.assignedRole = assignedRole; + this.assignedRole = of(assignedRole, Role::name); return this; } public void setAssignedRole(Role assignedRole) { - this.assignedRole = assignedRole; + this.assignedRole = of(assignedRole, Role::name); } public User getUser() { @@ -154,9 +160,9 @@ public class UserRoleAssignment implements AccessMappings { public String toString() { return "UserRoleAssignment{" + "id=" + getId() + - ", entityTypeId='" + getEntityTypeId() + "'" + - ", entityObjectId=" + getEntityObjectId() + - ", assignedRole='" + getAssignedRole() + "'" + + ", entityTypeId='" + entityTypeId + "'" + + ", entityObjectId=" + entityObjectId + + ", assignedRole='" + assignedRole + "'" + "}"; } @@ -172,9 +178,8 @@ public class UserRoleAssignment implements AccessMappings { @Override protected JSonFieldWriter jsonFieldWriter(final Field field) { if (USER_FIELD_NAME.equals(field.getName())) { - return (final UserRoleAssignment dto, final JsonGenerator jsonGenerator) -> { - jsonGenerator.writeNumberField(USER_FIELD_NAME, dto.getUser().getId()); - }; + return (final UserRoleAssignment dto, final JsonGenerator jsonGenerator) -> jsonGenerator + .writeNumberField(USER_FIELD_NAME, dto.getUser().getId()); } return super.jsonFieldWriter(field); } @@ -196,9 +201,8 @@ public class UserRoleAssignment implements AccessMappings { @Override protected JSonFieldReader jsonFieldReader(final TreeNode treeNode, final Field field) { if ("user".equals(field.getName())) { - return (final UserRoleAssignment target) -> { - target.setUser(userRepository.getOne(getSubNode(treeNode, "id").asLong())); - }; + return (final UserRoleAssignment target) -> target + .setUser(userRepository.getOne(getSubNode(treeNode, "id").asLong())); } return super.jsonFieldReader(treeNode, field); diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/AccessFor.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/AccessFor.java index 5e76c893..0d0a1a52 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/AccessFor.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/AccessFor.java @@ -1,6 +1,8 @@ // Licensed under Apache-2.0 package org.hostsharing.hsadminng.service.accessfilter; +import org.hostsharing.hsadminng.service.accessfilter.Role.Nobody; + import java.lang.annotation.*; @Documented @@ -8,9 +10,9 @@ import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) public @interface AccessFor { - Role[] init() default Role.NOBODY; + Class[] init() default Nobody.class; - Role[] update() default Role.NOBODY; + Class[] update() default Nobody.class; - Role[] read() default Role.NOBODY; + Class[] read() default Nobody.class; } diff --git a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java index b4564d3c..2e79d807 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JSonAccessFilter.java @@ -1,27 +1,28 @@ // Licensed under Apache-2.0 package org.hostsharing.hsadminng.service.accessfilter; -import static com.google.common.base.Verify.verify; -import static com.google.common.collect.Sets.union; -import static java.util.Collections.EMPTY_SET; -import static java.util.Collections.emptySet; - import org.hostsharing.hsadminng.security.SecurityUtils; import org.hostsharing.hsadminng.service.IdToDtoResolver; import org.hostsharing.hsadminng.service.UserRoleAssignmentService; import org.hostsharing.hsadminng.service.dto.MembershipDTO; import org.hostsharing.hsadminng.service.util.ReflectionUtil; import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException; - import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ApplicationContext; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; +import static com.google.common.base.Verify.verify; +import static com.google.common.collect.Sets.union; +import static java.util.Collections.EMPTY_SET; +import static java.util.Collections.emptySet; + abstract class JSonAccessFilter { private final ApplicationContext ctx; @@ -58,11 +59,15 @@ abstract class JSonAccessFilter { * @return all roles of the login user in relation to the dto, for which this filter is created. */ Set getLoginUserRoles() { - final Set independentRoles = Arrays.stream(Role.values()) - .filter( - role -> role.getAuthority() - .map(authority -> SecurityUtils.isCurrentUserInRole(authority)) - .orElse(false)) + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + return emptySet(); + } + final Set independentRoles = authentication + .getAuthorities() + .stream() + .map(GrantedAuthority::getAuthority) + .map(Role::of) .collect(Collectors.toSet()); final Set rolesOnThis = getId() != null ? getLoginUserDirectRolesFor(dto.getClass(), getId()) : EMPTY_SET; @@ -93,14 +98,10 @@ abstract class JSonAccessFilter { } private Set getLoginUserDirectRolesFor(final Class dtoClass, final long id) { - if (!SecurityUtils.isAuthenticated()) { - return emptySet(); - } + verify(SecurityUtils.isAuthenticated()); final EntityTypeId entityTypeId = dtoClass.getAnnotation(EntityTypeId.class); - if (entityTypeId == null) { - return emptySet(); - } + verify(entityTypeId != null, "@" + EntityTypeId.class.getSimpleName() + " missing on " + dtoClass.getName()); return userRoleAssignmentService.getEffectiveRoleOfCurrentUser(entityTypeId.value(), id); } 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 f4a05c52..d4b90c67 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JsonDeserializerWithAccessFilter.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JsonDeserializerWithAccessFilter.java @@ -1,13 +1,6 @@ // Licensed under Apache-2.0 package org.hostsharing.hsadminng.service.accessfilter; -import static com.google.common.base.Verify.verify; -import static org.hostsharing.hsadminng.service.util.ReflectionUtil.unchecked; - -import org.hostsharing.hsadminng.service.UserRoleAssignmentService; -import org.hostsharing.hsadminng.service.util.ReflectionUtil; -import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException; - import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.DeserializationContext; @@ -15,9 +8,11 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.*; import com.google.common.base.Joiner; - import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.ObjectUtils; +import org.hostsharing.hsadminng.service.UserRoleAssignmentService; +import org.hostsharing.hsadminng.service.util.ReflectionUtil; +import org.hostsharing.hsadminng.web.rest.errors.BadRequestAlertException; import org.springframework.context.ApplicationContext; import java.lang.reflect.Field; @@ -26,6 +21,9 @@ import java.time.LocalDate; import java.util.HashSet; import java.util.Set; +import static com.google.common.base.Verify.verify; +import static org.hostsharing.hsadminng.service.util.ReflectionUtil.unchecked; + public abstract class JsonDeserializerWithAccessFilter extends JsonDeserializer { private final ApplicationContext ctx; @@ -85,31 +83,30 @@ public abstract class JsonDeserializerWithAccessFilter private Object readValueFromJSon(final TreeNode treeNode, final String fieldName, final Class fieldClass) { // FIXME can be removed? final TreeNode fieldNode = treeNode.get(fieldName); - final TreeNode fieldNode = treeNode; - if (fieldNode instanceof NullNode) { + if (treeNode instanceof NullNode) { return null; } - if (fieldNode instanceof TextNode) { - return ((TextNode) fieldNode).asText(); + if (treeNode instanceof TextNode) { + return ((TextNode) treeNode).asText(); } - if (fieldNode instanceof IntNode) { - return ((IntNode) fieldNode).asInt(); + if (treeNode instanceof IntNode) { + return ((IntNode) treeNode).asInt(); } - if (fieldNode instanceof LongNode) { - return ((LongNode) fieldNode).asLong(); + if (treeNode instanceof LongNode) { + return ((LongNode) treeNode).asLong(); } - if (fieldNode instanceof DoubleNode) { + if (treeNode instanceof DoubleNode) { // TODO: we need to figure out, why DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS does not work - return ((DoubleNode) fieldNode).asDouble(); + return ((DoubleNode) treeNode).asDouble(); } - if (fieldNode instanceof ArrayNode && LocalDate.class.isAssignableFrom(fieldClass)) { + if (treeNode instanceof ArrayNode && LocalDate.class.isAssignableFrom(fieldClass)) { return LocalDate.of( - ((ArrayNode) fieldNode).get(0).asInt(), - ((ArrayNode) fieldNode).get(1).asInt(), - ((ArrayNode) fieldNode).get(2).asInt()); + ((ArrayNode) treeNode).get(0).asInt(), + ((ArrayNode) treeNode).get(1).asInt(), + ((ArrayNode) treeNode).get(2).asInt()); } throw new NotImplementedException( - "JSon node type not implemented: " + fieldNode.getClass() + " -> " + fieldName + ": " + fieldClass); + "JSon node type not implemented: " + treeNode.getClass() + " -> " + fieldName + ": " + fieldClass); } private void writeValueToDto(final T dto, final Field field, final Object value) { @@ -223,25 +220,29 @@ public abstract class JsonDeserializerWithAccessFilter throw new BadRequestAlertException( "Initialization of field " + toDisplay(field) + " prohibited for current user role(s): " - + Joiner.on("+").join(roles), + + asString(roles), toDisplay(field), "initializationProhibited"); } else { throw new BadRequestAlertException( "Referencing field " + toDisplay(field) + " prohibited for current user role(s): " - + Joiner.on("+").join(roles), + + asString(roles), toDisplay(field), "referencingProhibited"); } } } + private String asString(Set roles) { + return Joiner.on("+").join(roles.stream().map(Role::name).toArray()); + } + private void validateUpdateAccess(Field field, Set roles) { if (!Role.toBeIgnoredForUpdates(field) && !isAllowedToUpdate(getLoginUserRoles(), field)) { throw new BadRequestAlertException( "Update of field " + toDisplay(field) + " prohibited for current user role(s): " - + Joiner.on("+").join(roles), + + asString(roles), toDisplay(field), "updateProhibited"); } 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 1779d6c9..e74f480f 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JsonSerializerWithAccessFilter.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/JsonSerializerWithAccessFilter.java @@ -1,14 +1,12 @@ // Licensed under Apache-2.0 package org.hostsharing.hsadminng.service.accessfilter; -import org.hostsharing.hsadminng.service.UserRoleAssignmentService; -import org.hostsharing.hsadminng.service.util.ReflectionUtil; - 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.service.UserRoleAssignmentService; +import org.hostsharing.hsadminng.service.util.ReflectionUtil; import org.springframework.context.ApplicationContext; import java.io.IOException; @@ -131,7 +129,7 @@ public abstract class JsonSerializerWithAccessFilter e return true; } } - return Role.ANYBODY.isAllowedToRead(field); + return ReflectionUtil.newInstance(Role.Anybody.class).isAllowedToRead(field); // TODO } } 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 dcf64b1f..258a34b4 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/accessfilter/Role.java +++ b/src/main/java/org/hostsharing/hsadminng/service/accessfilter/Role.java @@ -1,35 +1,128 @@ // Licensed under Apache-2.0 package org.hostsharing.hsadminng.service.accessfilter; -import static com.google.common.base.Verify.verify; - +import org.apache.commons.lang3.ArrayUtils; import org.hostsharing.hsadminng.domain.Customer; import org.hostsharing.hsadminng.domain.User; import org.hostsharing.hsadminng.domain.UserRoleAssignment; import org.hostsharing.hsadminng.security.AuthoritiesConstants; +import org.hostsharing.hsadminng.service.util.ReflectionUtil; import java.lang.reflect.Field; -import java.util.Optional; +import java.util.HashMap; +import java.util.Map; + +import static com.google.common.base.Verify.verify; +import static org.hostsharing.hsadminng.service.util.ReflectionUtil.initialize; /** * These enum values are used to specify the minimum role required to grant access to resources, * see usages of {@link AccessFor}. - * also they can be assigned to users via {@link UserRoleAssignment}. + * Also they can be assigned to users via {@link UserRoleAssignment}. * Some of the concrete values make only sense in one of these contexts. *

- * Further, there are two kinds of roles: independent and dependent. - * Independent roles like {@link #HOSTMASTER} are absolute roles which means unrelated to any concrete entity. - * Dependent roles like {@link #CUSTOMER_CONTRACTUAL_CONTACT} are relative to a specific entity, + * There are two kinds of roles: independent and dependent. + * Independent roles like {@link Hostmaster} are absolute roles which means unrelated to any concrete entity. + * Dependent roles like {@link CustomerContractualContact} are relative to a specific entity, * in this case to a specific {@link Customer}. *

+ *

+ * Separate classes are used to make it possible to use roles in Java annotations + * and also make it possible to have roles spread over multiple modules. + *

*/ -/* - * TODO: Maybe splitting it up into UserRole and RequiredRole would make it more clear? - * And maybe instead of a level, we could then add the comprised roles in the constructor? - * This could also be a better way to express that the financial contact has no rights to - * other users resources (see also ACTUAL_CUSTOMER_USER vs. ANY_CUSTOMER_USER). - */ -public enum Role { +public abstract class Role { + + // TODO mhoennig: We need to make sure that the classes are loaded + // and thus the static initializers were called + // before these maps are used in production code. + private static Map, Role> rolesByClass = new HashMap<>(); + private static Map rolesByName = new HashMap<>(); + + private final String authority; + private final LazyRoles comprises; + + Role() { + this.authority = AuthoritiesConstants.USER; + // noinspection unchecked + this.comprises = new LazyRoles(); + } + + @SafeVarargs + Role(final Class... comprisedRoleClasses) { + this.authority = AuthoritiesConstants.USER; + // noinspection unchecked + this.comprises = new LazyRoles(comprisedRoleClasses); + } + + @SafeVarargs + Role(final String authority, final Class... comprisedRoleClasses) { + this.authority = authority; + // noinspection unchecked + this.comprises = new LazyRoles(comprisedRoleClasses); + } + + public static Role of(final String authority) { + final Role role = rolesByName.get(authority); + verify( + role != null, + "unknown authority: %s, available authorities: ", + authority, + ArrayUtils.toString(rolesByName.keySet())); + return role; + } + + public static T of(final Class roleClass) { + // prevent initialization and thus recursive call to `Role.of(...)` within `newInstance(...)` + final Class initializedRoleClass = initialize(roleClass); + { + final T role = (T) rolesByClass.get(initializedRoleClass); + if (role != null) { + return role; + } + } + { + T newRole = (T) ReflectionUtil.newInstance(initializedRoleClass); + rolesByClass.put(initializedRoleClass, newRole); + rolesByName.put(newRole.name(), newRole); + return newRole; + } + } + + @Override + public String toString() { + return getClass().getName() + "(" + name() + ")"; + } + + public abstract String name(); + + public static class IndependentRole extends Role { + + @SafeVarargs + IndependentRole(final String authority, final Class... comprisedRoleClasses) { + super(authority, comprisedRoleClasses); + } + + public String name() { + return authority(); + } + } + + public static class DependentRole extends Role { + + DependentRole() { + } + + @SafeVarargs + DependentRole(final Class... comprisedRoleClasses) { + super(AuthoritiesConstants.USER, comprisedRoleClasses); + } + + public String name() { + return getClass().getSimpleName(); // TODO: decide if it's ok for use in the DB table + } + } + /** * Default for access rights requirement. You can read it as: 'Nobody is allowed to ...'. * This is usually used for fields which are managed by hsadminNg itself. @@ -37,31 +130,43 @@ public enum Role { * This role cannot be assigned to a user. *

*/ - NOBODY(0), + public static class Nobody extends DependentRole { + + public static final Nobody ROLE = Role.of(Nobody.class); + } /** * Hostmasters are initialize/update/read and field which, except where NOBODY is allowed to. - *

- * This role can be assigned to a user via {@link User#setAuthorities}. - *

*/ - HOSTMASTER(1, AuthoritiesConstants.HOSTMASTER), + public static class Hostmaster extends IndependentRole { - /** - * This role is for administrators, e.g. to create memberships and book shared and assets. - *

- * This role can be assigned to a user via {@link User#setAuthorities}. - *

- */ - ADMIN(2, AuthoritiesConstants.ADMIN), + /** + * Hostmasters role to be assigned to users via via {@link User#setAuthorities}. + */ + public static final Hostmaster ROLE = Role.of(Hostmaster.class); - /** - * This role is for members of the support team. - *

- * This role can be assigned to a user via {@link User#setAuthorities}. - *

- */ - SUPPORTER(3, AuthoritiesConstants.SUPPORTER), + Hostmaster() { + super(AuthoritiesConstants.HOSTMASTER, Admin.class); + } + } + + public static class Admin extends IndependentRole { + + public static final Admin ROLE = Role.of(Admin.class); + + Admin() { + super(AuthoritiesConstants.ADMIN, Supporter.class); + } + } + + public static class Supporter extends IndependentRole { + + public static final Supporter ROLE = Role.of(Supporter.class); + + Supporter() { + super(AuthoritiesConstants.SUPPORTER, CustomerContractualContact.class); + } + } /** * This role is for contractual contacts of a customer, like a director of the company. @@ -72,66 +177,76 @@ public enum Role { * This role can be assigned to a user via {@link UserRoleAssignment}. *

*/ - CUSTOMER_CONTRACTUAL_CONTACT(20), + public static class CustomerContractualContact extends DependentRole { - /** - * This role is for financial contacts of a customer, e.g. for accessing billing data. - *

- * The financial contact only covers {@link Role#CUSTOMER_FINANCIAL_CONTACT}, {@link Role#ANY_CUSTOMER_CONTACT} and - * {@link Role#ANYBODY}, but not other normal user roles. - *

- *

- * This role can be assigned to a user via {@link UserRoleAssignment}. - *

- */ - CUSTOMER_FINANCIAL_CONTACT(22) { + public static final CustomerContractualContact ROLE = Role.of(CustomerContractualContact.class); - @Override - public boolean covers(final Role role) { - return role == CUSTOMER_FINANCIAL_CONTACT || role == ANY_CUSTOMER_CONTACT || role == ANYBODY; + CustomerContractualContact() { + super(CustomerFinancialContact.class, CustomerTechnicalContact.class); } - }, + } - /** - * This role is for technical contacts of a customer. - *

- * This role can be assigned to a user via {@link UserRoleAssignment}. - *

- */ - CUSTOMER_TECHNICAL_CONTACT(22), + public static class CustomerFinancialContact extends DependentRole { - /** - * This meta-role is to specify that any kind of customer contact can get access to the resource. - *

- * It's only used to specify the required role and cannot be assigned to a user. - *

- */ - ANY_CUSTOMER_CONTACT(29), + public static final CustomerFinancialContact ROLE = Role.of(CustomerFinancialContact.class); - /** - * Some user belonging to a customer without a more precise role. - */ - // TODO: It's mostly a placeholder for more precise future roles like a "webspace admin". - // This also shows that it's a bit ugly that we need the roles of all modules in this enum - // because types for attributes of annotations are quite limited in Java. - ACTUAL_CUSTOMER_USER(80), + CustomerFinancialContact() { + super(AnyCustomerContact.class); + } + } - /** - * Use this to grant rights to any user, also special function users who have no - * rights on other users resources. - *

- * It's only used to specify the required role and cannot be assigned to a user. - *

- */ - ANY_CUSTOMER_USER(89), + public static class CustomerTechnicalContact extends DependentRole { + + public static final CustomerTechnicalContact ROLE = Role.of(CustomerTechnicalContact.class); + + CustomerTechnicalContact() { + super( + AnyCustomerContact.class, + AnyCustomerUser.class); // TODO mhoennig: how to add roles of other modules? + } + } + + public static class AnyCustomerContact extends DependentRole { + + public static final AnyCustomerContact ROLE = Role.of(AnyCustomerContact.class); + + AnyCustomerContact() { + super(Anybody.class); + } + } + + public static class ActualCustomerUser extends DependentRole { + + public static final ActualCustomerUser ROLE = Role.of(ActualCustomerUser.class); + + ActualCustomerUser() { + super(AnyCustomerUser.class); + } + } + + public static class AnyCustomerUser extends DependentRole { + + public static final Role ROLE = Role.of(AnyCustomerUser.class); + + AnyCustomerUser() { + super(Anybody.class); + } + } /** * This role is meant to specify that a resources can be accessed by anybody, even without login. *

- * It can be used to specify the required role and is the implicit role for un-authenticated users. + * It can be used to specify to grant rights to any use, even if unauthorized. *

*/ - ANYBODY(99, AuthoritiesConstants.ANONYMOUS), + public static class Anybody extends IndependentRole { + + public static final Role ROLE = Role.of(Anybody.class); + + Anybody() { + super(AuthoritiesConstants.ANONYMOUS); + } + } /** * Pseudo-role to mark init/update access as ignored because the field is display-only. @@ -139,27 +254,12 @@ public enum Role { * This allows REST clients to send the whole response back as a new update request. * This role is not covered by any and covers itself no role. *

- * It's only used to specify the required role and cannot be assigned to a user. + * It's only used to ignore the field. *

*/ - IGNORED; + public static class Ignored extends DependentRole { - private final Integer level; - private final Optional authority; - - Role(final int level, final String authority) { - this.level = level; - this.authority = Optional.of(authority); - } - - Role(final int level) { - this.level = level; - this.authority = Optional.empty(); - } - - Role() { - this.level = null; - this.authority = Optional.empty(); + public static final Role ROLE = Role.of(Ignored.class); } /** @@ -171,32 +271,25 @@ public enum Role { if (accessForAnnot == null) { return true; } - final Role[] updateAccessFor = field.getAnnotation(AccessFor.class).update(); - return updateAccessFor.length == 1 && updateAccessFor[0].isIgnored(); + final Class[] updateAccessFor = field.getAnnotation(AccessFor.class).update(); + return updateAccessFor.length == 1 && updateAccessFor[0] == Ignored.class; } /** * @return the independent authority related 1:1 to this Role or empty if no independent authority is related 1:1 * @see AuthoritiesConstants */ - public Optional getAuthority() { + public String authority() { return authority; } - /** - * @return true if the role is the IGNORED role - */ - public boolean isIgnored() { - return this == Role.IGNORED; - } - /** * @return the role with the broadest access rights */ public static Role broadest(final Role role, final Role... roles) { Role broadests = role; for (Role r : roles) { - if (r.covers(broadests)) { + if (r.covers(broadests.getClass())) { broadests = r; } } @@ -209,17 +302,25 @@ public enum Role { * Where 'this' means the Java instance itself as a role of a system user. *

* {@code - * Role.HOSTMASTER.covers(Role.ANY_CUSTOMER_USER) == true + * AssignedHostmaster.ROLE.covers(AssignedRole.ANY_CUSTOMER_USER) == true * } * - * @param role The required role for a resource. + * @param roleClass The required role for a resource. * @return whether this role comprises the given role */ - public boolean covers(final Role role) { - if (this.isIgnored() || role.isIgnored()) { + public boolean covers(final Class roleClass) { + if (getClass() == Ignored.class || roleClass == Ignored.class) { return false; } - return this == role || this.level < role.level; + if (getClass() == roleClass) { + return true; + } + for (Role role : comprises.get()) { + if (role.covers(roleClass)) { + return true; + } + } + return false; } /** @@ -228,17 +329,17 @@ public enum Role { * Where 'this' means the Java instance itself as a role of a system user. *

* {@code - * Role.HOSTMASTER.coversAny(Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT) == true + * AssignedHostmaster.ROLE.coversAny(AssignedRole.CUSTOMER_CONTRACTUAL_CONTACT, AssignedRole.CUSTOMER_FINANCIAL_CONTACT) == true * } * - * @param roles The alternatively required roles for a resource. Must be at least one. + * @param roleClasses The alternatively required roles for a resource. Must be at least one. * @return whether this role comprises any of the given roles */ - public boolean coversAny(final Role... roles) { - verify(roles != null && roles.length > 0, "roles expected"); + public boolean coversAny(final Class... roleClasses) { + verify(roleClasses != null && roleClasses.length > 0, "role classes expected"); - for (Role role : roles) { - if (this.covers(role)) { + for (Class roleClass : roleClasses) { + if (this.covers(roleClass)) { return true; } } @@ -258,7 +359,7 @@ public enum Role { return false; } - return isRoleCovered(accessFor.init()); + return coversAny(accessFor.init()); } /** @@ -274,7 +375,7 @@ public enum Role { return false; } - return isRoleCovered(accessFor.update()); + return coversAny(accessFor.update()); } /** @@ -290,15 +391,26 @@ public enum Role { return false; } - return isRoleCovered(accessFor.read()); - } - - private boolean isRoleCovered(final Role[] requiredRoles) { - for (Role accessAllowedForRole : requiredRoles) { - if (this.covers(accessAllowedForRole)) { - return true; - } - } - return false; + return coversAny(accessFor.read()); + } +} + +class LazyRoles { + + private final Class[] comprisedRoleClasses; + private Role[] comprisedRoles = null; + + LazyRoles(Class... comprisedRoleClasses) { + this.comprisedRoleClasses = comprisedRoleClasses; + } + + Role[] get() { + if (comprisedRoles == null) { + comprisedRoles = new Role[comprisedRoleClasses.length]; + for (int n = 0; n < comprisedRoleClasses.length; ++n) { + comprisedRoles[n] = Role.of(comprisedRoleClasses[n]); + } + } + return comprisedRoles; } } diff --git a/src/main/java/org/hostsharing/hsadminng/service/dto/AssetDTO.java b/src/main/java/org/hostsharing/hsadminng/service/dto/AssetDTO.java index ead04d23..54103026 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/AssetDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/AssetDTO.java @@ -7,18 +7,17 @@ import org.hostsharing.hsadminng.service.AssetService; import org.hostsharing.hsadminng.service.MembershipService; import org.hostsharing.hsadminng.service.UserRoleAssignmentService; import org.hostsharing.hsadminng.service.accessfilter.*; - +import org.hostsharing.hsadminng.service.accessfilter.Role.*; import org.springframework.boot.jackson.JsonComponent; import org.springframework.context.ApplicationContext; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; import java.util.Objects; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; - /** * A DTO for the Asset entity. */ @@ -26,34 +25,34 @@ import javax.validation.constraints.Size; public class AssetDTO implements Serializable, AccessMappings { @SelfId(resolver = AssetService.class) - @AccessFor(read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private Long id; @NotNull - @AccessFor(init = Role.ADMIN, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(init = Admin.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate documentDate; @NotNull - @AccessFor(init = Role.ADMIN, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(init = Admin.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate valueDate; @NotNull - @AccessFor(init = Role.ADMIN, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(init = Admin.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private AssetAction action; @NotNull - @AccessFor(init = Role.ADMIN, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(init = Admin.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private BigDecimal amount; @Size(max = 160) - @AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = Role.SUPPORTER) + @AccessFor(init = Admin.class, update = Admin.class, read = Supporter.class) private String remark; @ParentId(resolver = MembershipService.class) - @AccessFor(init = Role.ADMIN, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(init = Admin.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private Long membershipId; - @AccessFor(update = Role.IGNORED, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(update = Ignored.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private String membershipDisplayLabel; public Long getId() { diff --git a/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java b/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java index 2612a345..19d6e48b 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/CustomerDTO.java @@ -7,14 +7,14 @@ import org.hostsharing.hsadminng.domain.enumeration.VatRegion; import org.hostsharing.hsadminng.service.CustomerService; import org.hostsharing.hsadminng.service.UserRoleAssignmentService; import org.hostsharing.hsadminng.service.accessfilter.*; - import org.springframework.boot.jackson.JsonComponent; import org.springframework.context.ApplicationContext; +import javax.validation.constraints.*; import java.time.LocalDate; import java.util.Objects; -import javax.validation.constraints.*; +import static org.hostsharing.hsadminng.service.accessfilter.Role.*; /** * A DTO for the Customer entity. @@ -23,99 +23,99 @@ import javax.validation.constraints.*; public class CustomerDTO implements AccessMappings, FluentBuilder { @SelfId(resolver = CustomerService.class) - @AccessFor(read = Role.ANY_CUSTOMER_USER) + @AccessFor(read = AnyCustomerUser.class) private Long id; @NotNull @Min(value = 10000) @Max(value = 99999) - @AccessFor(init = Role.ADMIN, read = Role.ANY_CUSTOMER_USER) + @AccessFor(init = Admin.class, read = AnyCustomerUser.class) private Integer reference; @NotNull @Size(max = 3) @Pattern(regexp = "[a-z][a-z0-9]+") - @AccessFor(init = Role.ADMIN, read = Role.ANY_CUSTOMER_USER) + @AccessFor(init = Admin.class, read = AnyCustomerUser.class) private String prefix; @NotNull @Size(max = 80) - @AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = Role.ANY_CUSTOMER_USER) + @AccessFor(init = Admin.class, update = Admin.class, read = AnyCustomerUser.class) private String name; @NotNull - @AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = Role.CUSTOMER_CONTRACTUAL_CONTACT) + @AccessFor(init = Admin.class, update = Admin.class, read = CustomerContractualContact.class) private CustomerKind kind; @AccessFor( - init = Role.ADMIN, - update = Role.ADMIN, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = Admin.class, + update = Admin.class, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate birthDate; @Size(max = 80) @AccessFor( - init = Role.ADMIN, - update = Role.ADMIN, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = Admin.class, + update = Admin.class, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private String birthPlace; @Size(max = 80) @AccessFor( - init = Role.ADMIN, - update = Role.ADMIN, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = Admin.class, + update = Admin.class, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private String registrationCourt; @Size(max = 80) @AccessFor( - init = Role.ADMIN, - update = Role.ADMIN, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = Admin.class, + update = Admin.class, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private String registrationNumber; @NotNull @AccessFor( - init = Role.ADMIN, - update = Role.ADMIN, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = Admin.class, + update = Admin.class, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private VatRegion vatRegion; @Size(max = 40) @AccessFor( - init = Role.ADMIN, - update = Role.ADMIN, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = Admin.class, + update = Admin.class, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private String vatNumber; @Size(max = 80) - @AccessFor(init = Role.ADMIN, update = Role.CUSTOMER_CONTRACTUAL_CONTACT, read = Role.CUSTOMER_CONTRACTUAL_CONTACT) + @AccessFor(init = Admin.class, update = CustomerContractualContact.class, read = CustomerContractualContact.class) private String contractualSalutation; @NotNull @Size(max = 400) - @AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = Role.CUSTOMER_CONTRACTUAL_CONTACT) + @AccessFor(init = Admin.class, update = Admin.class, read = CustomerContractualContact.class) private String contractualAddress; @Size(max = 80) @AccessFor( - init = Role.ADMIN, - update = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }, - read = Role.CUSTOMER_CONTRACTUAL_CONTACT) + init = Admin.class, + update = { CustomerContractualContact.class, CustomerFinancialContact.class }, + read = CustomerContractualContact.class) private String billingSalutation; @Size(max = 400) @AccessFor( - init = Role.ADMIN, - update = Role.ADMIN, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = Admin.class, + update = Admin.class, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private String billingAddress; @Size(max = 160) - @AccessFor(init = Role.ADMIN, update = Role.SUPPORTER, read = Role.SUPPORTER) + @AccessFor(init = Admin.class, update = Supporter.class, read = Supporter.class) private String remark; - @AccessFor(init = Role.ANYBODY, update = Role.ANYBODY, read = Role.ANY_CUSTOMER_USER) + @AccessFor(init = Anybody.class, update = Anybody.class, read = AnyCustomerUser.class) private String displayLabel; public Long getId() { 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 6aba48ab..75f13277 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/MembershipDTO.java @@ -6,15 +6,14 @@ import org.hostsharing.hsadminng.service.CustomerService; import org.hostsharing.hsadminng.service.MembershipService; import org.hostsharing.hsadminng.service.UserRoleAssignmentService; import org.hostsharing.hsadminng.service.accessfilter.*; - +import org.hostsharing.hsadminng.service.accessfilter.Role.*; import org.springframework.boot.jackson.JsonComponent; import org.springframework.context.ApplicationContext; -import java.time.LocalDate; -import java.util.Objects; - import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import java.time.LocalDate; +import java.util.Objects; /** * A DTO for the Membership entity. @@ -23,44 +22,44 @@ import javax.validation.constraints.Size; public class MembershipDTO implements AccessMappings, FluentBuilder { @SelfId(resolver = MembershipService.class) - @AccessFor(read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private Long id; @NotNull - @AccessFor(init = Role.ADMIN, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(init = Admin.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate admissionDocumentDate; @AccessFor( - init = Role.ADMIN, - update = Role.ADMIN, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = Admin.class, + update = Admin.class, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate cancellationDocumentDate; @NotNull - @AccessFor(init = Role.ADMIN, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(init = Admin.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate memberFromDate; @AccessFor( - init = Role.ADMIN, - update = Role.ADMIN, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = Admin.class, + update = Admin.class, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate memberUntilDate; @Size(max = 160) - @AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = Role.SUPPORTER) + @AccessFor(init = Admin.class, update = Admin.class, read = Supporter.class) private String remark; @ParentId(resolver = CustomerService.class) - @AccessFor(init = Role.ADMIN, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(init = Admin.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private Long customerId; - @AccessFor(update = Role.IGNORED, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(update = Ignored.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private String customerPrefix; - @AccessFor(update = Role.IGNORED, read = Role.CUSTOMER_FINANCIAL_CONTACT) + @AccessFor(update = Ignored.class, read = CustomerFinancialContact.class) private String customerDisplayLabel; - @AccessFor(update = Role.IGNORED, read = Role.CUSTOMER_FINANCIAL_CONTACT) + @AccessFor(update = Ignored.class, read = CustomerFinancialContact.class) private String displayLabel; public Long getId() { diff --git a/src/main/java/org/hostsharing/hsadminng/service/dto/SepaMandateDTO.java b/src/main/java/org/hostsharing/hsadminng/service/dto/SepaMandateDTO.java index eddc61fd..7ff11375 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/SepaMandateDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/SepaMandateDTO.java @@ -6,15 +6,14 @@ import org.hostsharing.hsadminng.service.CustomerService; import org.hostsharing.hsadminng.service.SepaMandateService; import org.hostsharing.hsadminng.service.UserRoleAssignmentService; import org.hostsharing.hsadminng.service.accessfilter.*; - +import org.hostsharing.hsadminng.service.accessfilter.Role.*; import org.springframework.boot.jackson.JsonComponent; import org.springframework.context.ApplicationContext; -import java.time.LocalDate; -import java.util.Objects; - import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import java.time.LocalDate; +import java.util.Objects; /** * A DTO for the SepaMandate entity. @@ -23,69 +22,69 @@ import javax.validation.constraints.Size; public class SepaMandateDTO implements AccessMappings, FluentBuilder { @SelfId(resolver = SepaMandateService.class) - @AccessFor(read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private Long id; @NotNull @Size(max = 40) @AccessFor( - init = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = { CustomerContractualContact.class, CustomerFinancialContact.class }, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private String reference; @Size(max = 34) @AccessFor( - init = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = { CustomerContractualContact.class, CustomerFinancialContact.class }, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private String iban; @Size(max = 11) @AccessFor( - init = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = { CustomerContractualContact.class, CustomerFinancialContact.class }, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private String bic; @NotNull @AccessFor( - init = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = { CustomerContractualContact.class, CustomerFinancialContact.class }, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate grantingDocumentDate; @AccessFor( - init = Role.ADMIN, - update = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = Admin.class, + update = { CustomerContractualContact.class, CustomerFinancialContact.class }, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate revokationDocumentDate; @NotNull @AccessFor( - init = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = { CustomerContractualContact.class, CustomerFinancialContact.class }, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate validFromDate; @AccessFor( - init = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }, - update = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = { CustomerContractualContact.class, CustomerFinancialContact.class }, + update = { CustomerContractualContact.class, CustomerFinancialContact.class }, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate validUntilDate; @AccessFor( - init = Role.ADMIN, - update = Role.ADMIN, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = Admin.class, + update = Admin.class, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate lastUsedDate; @Size(max = 160) - @AccessFor(init = Role.ADMIN, update = Role.SUPPORTER, read = Role.SUPPORTER) + @AccessFor(init = Admin.class, update = Supporter.class, read = Supporter.class) private String remark; @ParentId(resolver = CustomerService.class) @AccessFor( - init = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }, - read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + init = { CustomerContractualContact.class, CustomerFinancialContact.class }, + read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private Long customerId; - @AccessFor(update = Role.IGNORED, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(update = Ignored.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private String customerDisplayLabel; public Long getId() { diff --git a/src/main/java/org/hostsharing/hsadminng/service/dto/ShareDTO.java b/src/main/java/org/hostsharing/hsadminng/service/dto/ShareDTO.java index 999e2212..901d3864 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/ShareDTO.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/ShareDTO.java @@ -7,17 +7,16 @@ import org.hostsharing.hsadminng.service.MembershipService; import org.hostsharing.hsadminng.service.ShareService; import org.hostsharing.hsadminng.service.UserRoleAssignmentService; import org.hostsharing.hsadminng.service.accessfilter.*; - +import org.hostsharing.hsadminng.service.accessfilter.Role.*; import org.springframework.boot.jackson.JsonComponent; import org.springframework.context.ApplicationContext; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; import java.time.LocalDate; import java.util.Objects; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; - /** * A DTO for the Share entity. */ @@ -25,34 +24,34 @@ import javax.validation.constraints.Size; public class ShareDTO implements Serializable, AccessMappings { @SelfId(resolver = ShareService.class) - @AccessFor(read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private Long id; @NotNull - @AccessFor(init = Role.ADMIN, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(init = Admin.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate documentDate; @NotNull - @AccessFor(init = Role.ADMIN, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(init = Admin.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private LocalDate valueDate; @NotNull - @AccessFor(init = Role.ADMIN, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(init = Admin.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private ShareAction action; @NotNull - @AccessFor(init = Role.ADMIN, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(init = Admin.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private Integer quantity; @Size(max = 160) - @AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = Role.SUPPORTER) + @AccessFor(init = Admin.class, update = Admin.class, read = Supporter.class) private String remark; @ParentId(resolver = MembershipService.class) - @AccessFor(init = Role.ADMIN, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(init = Admin.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private Long membershipId; - @AccessFor(update = Role.IGNORED, read = { Role.CUSTOMER_CONTRACTUAL_CONTACT, Role.CUSTOMER_FINANCIAL_CONTACT }) + @AccessFor(update = Ignored.class, read = { CustomerContractualContact.class, CustomerFinancialContact.class }) private String membershipDisplayLabel; public Long getId() { diff --git a/src/main/java/org/hostsharing/hsadminng/service/dto/UserRoleAssignmentCriteria.java b/src/main/java/org/hostsharing/hsadminng/service/dto/UserRoleAssignmentCriteria.java index 6a9d0e14..5f7a6acc 100644 --- a/src/main/java/org/hostsharing/hsadminng/service/dto/UserRoleAssignmentCriteria.java +++ b/src/main/java/org/hostsharing/hsadminng/service/dto/UserRoleAssignmentCriteria.java @@ -1,8 +1,6 @@ // Licensed under Apache-2.0 package org.hostsharing.hsadminng.service.dto; -import org.hostsharing.hsadminng.service.accessfilter.Role; - import io.github.jhipster.service.filter.Filter; import io.github.jhipster.service.filter.LongFilter; import io.github.jhipster.service.filter.StringFilter; @@ -23,7 +21,7 @@ public class UserRoleAssignmentCriteria implements Serializable { /** * Class for filtering UserRole */ - public static class UserRoleFilter extends Filter { + private static class UserRoleFilter extends Filter { } private static final long serialVersionUID = 1L; 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 c8936c65..22e69417 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,14 @@ // Licensed under Apache-2.0 package org.hostsharing.hsadminng.service.util; +import org.hostsharing.hsadminng.service.accessfilter.Role; + +import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.util.Optional; +import java.util.function.Function; public class ReflectionUtil { @@ -131,12 +136,32 @@ public class ReflectionUtil { return Enum.valueOf((Class) type, value.toString()); } + public static Role newInstance(final Class clazz) { + return unchecked(() -> accessible(clazz.getDeclaredConstructor()).newInstance()); + } + @FunctionalInterface public interface ThrowingSupplier { T get() throws Exception; } + /** + * Makes the given object accessible as if it were public. + * + * @param accessible field or method + * @param type of accessible + * @return the given object + */ + private static T accessible(final T accessible) { + try { + accessible.setAccessible(true); + return accessible; + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + /** * Catches checked exceptions and wraps these into an unchecked RuntimeException. *

@@ -159,4 +184,35 @@ public class ReflectionUtil { throw new RuntimeException(e); } } + + /** + * Calling a method on a potentially null object. Similar to the ?: operator in Kotlin. + * + * @param source some object of type T + * @param f some function mapping T to R + * @param the source type + * @param the result type + * @return the result of f if source is not null, null otherwise + */ + public static R of(T source, Function f) { + return Optional.ofNullable(source).map(f).orElse(null); + } + + /** + * Forces the initialization of the given class, this means, static initialization takes place. + * + * If the class is already initialized, this methods does nothing. + * + * @param clazz the class to be initialized + * @return the initialized class + * + */ + public static Class initialize(Class clazz) { + try { + Class.forName(clazz.getName(), true, clazz.getClassLoader()); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); // Can't happen + } + return clazz; + } } diff --git a/src/main/webapp/app/entities/user-role-assignment/user-role-assignment.component.html b/src/main/webapp/app/entities/user-role-assignment/user-role-assignment.component.html index 22af5117..5510b4f7 100644 --- a/src/main/webapp/app/entities/user-role-assignment/user-role-assignment.component.html +++ b/src/main/webapp/app/entities/user-role-assignment/user-role-assignment.component.html @@ -30,7 +30,7 @@