1
0

split up AssetDTOUnitTest and AssetDTOIntTest, AccessMappingsUnitTestBase

This commit is contained in:
Michael Hoennig
2019-04-27 17:56:38 +02:00
parent 2fdb914f6d
commit 3abc201a8d
10 changed files with 508 additions and 208 deletions

View File

@ -130,9 +130,10 @@ public class JSonDeserializationWithAccessFilter<T> extends JSonAccessFilter<T>
private void checkAccessToWrittenFields(final T currentDto) {
writtenFields.forEach(field -> {
// TODO this ugly code needs cleanup
if (!field.equals(selfIdField)) {
final Role role = getLoginUserRole();
if (getId() == null) {
if (isInitAccess()) {
if (!role.isAllowedToInit(field)) {
if (!field.equals(parentIdField)) {
throw new BadRequestAlertException("Initialization of field " + toDisplay(field) + " prohibited for current user role " + role, toDisplay(field), "initializationProhibited");
@ -140,14 +141,18 @@ public class JSonDeserializationWithAccessFilter<T> extends JSonAccessFilter<T>
throw new BadRequestAlertException("Referencing field " + toDisplay(field) + " prohibited for current user role " + role, toDisplay(field), "referencingProhibited");
}
}
} else if (isUpdate(field, dto, currentDto) && !getLoginUserRole().isAllowedToUpdate(field)) {
} else if ( !Role.toBeIgnoredForUpdates(field) && isActuallyUpdated(field, dto, currentDto) && !getLoginUserRole().isAllowedToUpdate(field)) {
throw new BadRequestAlertException("Update of field " + toDisplay(field) + " prohibited for current user role " + role, toDisplay(field), "updateProhibited");
}
}
});
}
private boolean isUpdate(final Field field, final T dto, T currentDto) {
private boolean isInitAccess() {
return getId() == null;
}
private boolean isActuallyUpdated(final Field field, final T dto, T currentDto) {
return ObjectUtils.notEqual(ReflectionUtil.getValue(dto, field), ReflectionUtil.getValue(currentDto, field));
}
}

View File

@ -2,14 +2,16 @@ package org.hostsharing.hsadminng.service.accessfilter;
import java.lang.reflect.Field;
import static com.google.common.base.Verify.verify;
/**
* These enum values are on the one hand used to define the minimum role required to grant access to resources,
* but on the other hand also for the roles users can be assigned to.
*
* <p>
* 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).
* 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 {
/**
@ -79,14 +81,45 @@ public enum Role {
* This role is meant to specify that a resources can be accessed by anybody, even without login.
* It's currently only used for technical purposes.
*/
ANYBODY(99);
ANYBODY(99),
private final int level;
/**
* Pseudo-role to mark init/update access as ignored because the field is display-only.
* 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.
*/
IGNORED;
private final Integer level;
Role() {
this.level = null;
}
Role(final int level) {
this.level = level;
}
/**
* @param field a field of a DTO with AccessMappings
* @return true if update access can be ignored because the field is just for display anyway
*/
public static boolean toBeIgnoredForUpdates(final Field field) {
final AccessFor accessForAnnot = field.getAnnotation(AccessFor.class);
if (accessForAnnot == null) {
return true;
}
final Role[] updateAccessFor = field.getAnnotation(AccessFor.class).update();
return updateAccessFor.length == 1 && updateAccessFor[0].isIgnored();
}
/**
* @return true if the role is the IGNORED role
*/
public boolean isIgnored() {
return this == Role.IGNORED;
}
/**
* @return true if this role is independent of a target object, false otherwise.
*/
@ -95,12 +128,12 @@ public enum Role {
}
/**
@return the role with the broadest access rights
* @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)) {
for (Role r : roles) {
if (r.covers(broadests)) {
broadests = r;
}
}
@ -108,27 +141,51 @@ public enum Role {
}
/**
* Determines if the given role is covered by this role.
*
* Determines if 'this' actual role covered the given required role.
* <p>
* Where 'this' means the Java instance itself as a role of a system user.
*
* <p>
* {@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
*/
public boolean covers(final Role role) {
if (this.isIgnored() || role.isIgnored()) {
return false;
}
return this == role || this.level < role.level;
}
/**
* Determines if 'this' actual role covers any of the given required roles.
* <p>
* Where 'this' means the Java instance itself as a role of a system user.
* <p>
* {@code
* Role.HOSTMASTER.coversAny(Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT) == true
* }
*
* @param roles 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");
for (Role role : roles) {
if (this.covers(role)) {
return true;
}
}
return false;
}
/**
* Checks if this role of a user allows to initialize the given field when creating the resource.
*
* @param field a field of the DTO of a resource
*
* @return true if allowed
*/
public boolean isAllowedToInit(final Field field) {
@ -145,7 +202,6 @@ public enum Role {
* Checks if this role of a user allows to update the given field.
*
* @param field a field of the DTO of a resource
*
* @return true if allowed
*/
public boolean isAllowedToUpdate(final Field field) {
@ -162,7 +218,6 @@ public enum Role {
* Checks if this role of a user allows to read the given field.
*
* @param field a field of the DTO of a resource
*
* @return true if allowed
*/
public boolean isAllowedToRead(final Field field) {

View File

@ -20,23 +20,23 @@ import java.util.Objects;
public class AssetDTO implements Serializable, AccessMappings {
@SelfId(resolver = AssetService.class)
@AccessFor(read = Role.ANY_CUSTOMER_USER)
@AccessFor(read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
private Long id;
@NotNull
@AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
@AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
private LocalDate documentDate;
@NotNull
@AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
@AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
private LocalDate valueDate;
@NotNull
@AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
@AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
private AssetAction action;
@NotNull
@AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
@AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
private BigDecimal amount;
@Size(max = 160)
@ -44,12 +44,10 @@ public class AssetDTO implements Serializable, AccessMappings {
private String remark;
@ParentId(resolver = MembershipService.class)
@AccessFor(init = Role.ADMIN, update = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
@AccessFor(init = Role.ADMIN, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
private Long membershipId;
// TODO: these init/update rights actually mean "ignore", we might want to express this in a better way
// background: there is no converter for any display label in DTOs to entity field values anyway
@AccessFor(init=Role.ANYBODY, update = Role.ANYBODY, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
@AccessFor(update = Role.IGNORED, read = {Role.CONTRACTUAL_CONTACT, Role.FINANCIAL_CONTACT})
private String membershipDisplayLabel;
public Long getId() {
@ -147,7 +145,7 @@ public class AssetDTO implements Serializable, AccessMappings {
", amount=" + getAmount() +
", remark='" + getRemark() + "'" +
", membership=" + getMembershipId() +
", membership='" + getMembershipDisplayLabel() + "'" +
", membershipDisplayLabel='" + getMembershipDisplayLabel() + "'" +
"}";
}