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 extends Role>[] init() default Nobody.class;
- Role[] update() default Role.NOBODY;
+ Class extends Role>[] update() default Nobody.class;
- Role[] read() default Role.NOBODY;
+ Class extends Role>[] 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 extends Role>... comprisedRoleClasses) {
+ this.authority = AuthoritiesConstants.USER;
+ // noinspection unchecked
+ this.comprises = new LazyRoles(comprisedRoleClasses);
+ }
+
+ @SafeVarargs
+ Role(final String authority, final Class extends Role>... 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 extends Role>... comprisedRoleClasses) {
+ super(authority, comprisedRoleClasses);
+ }
+
+ public String name() {
+ return authority();
+ }
+ }
+
+ public static class DependentRole extends Role {
+
+ DependentRole() {
+ }
+
+ @SafeVarargs
+ DependentRole(final Class extends Role>... 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 extends Role>[] 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 extends Role> 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.
*
@@ -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 @@