RBAC generator with conditional grants used for REPRESENTATIVE-Relation #33

Merged
hsh-michaelhoennig merged 31 commits from rbac-generator-with-conditional-grants into master 2024-04-08 11:16:07 +02:00
19 changed files with 60 additions and 52 deletions
Showing only changes of commit e5c01e70f5 - Show all commits

View File

@ -1,15 +1,15 @@
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '3.1.7' id 'org.springframework.boot' version '3.2.4'
id 'io.spring.dependency-management' version '1.1.4' id 'io.spring.dependency-management' version '1.1.4'
id 'io.openapiprocessor.openapi-processor' version '2023.2' id 'io.openapiprocessor.openapi-processor' version '2023.2'
id 'com.github.jk1.dependency-license-report' version '2.5' id 'com.github.jk1.dependency-license-report' version '2.6'
id "org.owasp.dependencycheck" version "9.0.7" id "org.owasp.dependencycheck" version "9.0.10"
id "com.diffplug.spotless" version "6.23.3" id "com.diffplug.spotless" version "6.25.0"
id 'jacoco' id 'jacoco'
id 'info.solidsoft.pitest' version '1.15.0' id 'info.solidsoft.pitest' version '1.15.0'
id 'se.patrikerdes.use-latest-versions' version '0.2.18' id 'se.patrikerdes.use-latest-versions' version '0.2.18'
id 'com.github.ben-manes.versions' version '0.50.0' id 'com.github.ben-manes.versions' version '0.51.0'
} }
group = 'net.hostsharing' group = 'net.hostsharing'
@ -59,17 +59,17 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.9.1' implementation 'com.github.gavlyukovskiy:datasource-proxy-spring-boot-starter:1.9.1'
implementation 'org.springdoc:springdoc-openapi:2.3.0' implementation 'org.springdoc:springdoc-openapi:2.4.0'
implementation 'org.postgresql:postgresql:42.7.1' implementation 'org.postgresql:postgresql:42.7.3'
implementation 'org.liquibase:liquibase-core:4.25.1' implementation 'org.liquibase:liquibase-core:4.27.0'
implementation 'com.vladmihalcea:hibernate-types-60:2.21.1' //implementation 'com.vladmihalcea:hibernate-types-60:2.21.1'
implementation 'io.hypersistence:hypersistence-utils-hibernate-62:3.7.0' implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.7.3'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.1' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.0'
implementation 'org.openapitools:jackson-databind-nullable:0.2.6' implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
implementation 'org.apache.commons:commons-text:1.11.0' implementation 'org.apache.commons:commons-text:1.11.0'
implementation 'org.modelmapper:modelmapper:3.2.0' implementation 'org.modelmapper:modelmapper:3.2.0'
implementation 'org.iban4j:iban4j:3.2.7-RELEASE' implementation 'org.iban4j:iban4j:3.2.7-RELEASE'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.4.0'
// fixes vulnerability CVE-2022-1471 // fixes vulnerability CVE-2022-1471
// The dependency usually comes from Spring Boot, just in the wrong version. // The dependency usually comes from Spring Boot, just in the wrong version.

View File

@ -15,11 +15,9 @@ import java.util.Collections;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.util.function.Predicate.not; import static java.util.function.Predicate.not;
import static net.hostsharing.hsadminng.mapper.PostgresArray.fromPostgresArray;
import static org.springframework.transaction.annotation.Propagation.MANDATORY; import static org.springframework.transaction.annotation.Propagation.MANDATORY;
@Service @Service
@ -82,14 +80,11 @@ public class Context {
} }
public String[] getAssumedRoles() { public String[] getAssumedRoles() {
final byte[] result = (byte[]) em.createNativeQuery("select assumedRoles() as roles", String[].class).getSingleResult(); return (String[]) em.createNativeQuery("select assumedRoles() as roles", String[].class).getSingleResult();
return fromPostgresArray(result, String.class, Function.identity());
} }
public UUID[] currentSubjectsUuids() { public UUID[] currentSubjectsUuids() {
final byte[] result = (byte[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class) return (UUID[]) em.createNativeQuery("select currentSubjectsUuids() as uuids", UUID[].class).getSingleResult();
.getSingleResult();
return fromPostgresArray(result, UUID.class, UUID::fromString);
} }
public static String getCallerMethodNameFromStackFrame(final int skipFrames) { public static String getCallerMethodNameFromStackFrame(final int skipFrames) {

View File

@ -11,16 +11,18 @@ import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.orm.jpa.JpaObjectRetrievalFailureException; import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;
import org.springframework.orm.jpa.JpaSystemException; import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.validation.FieldError;
import org.springframework.validation.method.ParameterValidationResult;
import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.HandlerMethodValidationException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
import java.util.NoSuchElementException; import java.util.*;
import java.util.Optional;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static net.hostsharing.hsadminng.errors.CustomErrorResponse.*; import static net.hostsharing.hsadminng.errors.CustomErrorResponse.*;
@ -119,6 +121,28 @@ public class RestResponseEntityExceptionHandler
return errorResponse(request, HttpStatus.BAD_REQUEST, errorList.toString()); return errorResponse(request, HttpStatus.BAD_REQUEST, errorList.toString());
} }
@SuppressWarnings("unchecked,rawtypes")
@Override
protected ResponseEntity handleHandlerMethodValidationException(
final HandlerMethodValidationException exc,
final HttpHeaders headers,
final HttpStatusCode status,
final WebRequest request) {
final var errorList = exc
.getAllValidationResults()
.stream()
.map(ParameterValidationResult::getResolvableErrors)
.flatMap(Collection::stream)
.filter(FieldError.class::isInstance)
.map(FieldError.class::cast)
.map(fieldError -> fieldError.getField() + " " + fieldError.getDefaultMessage() + " but is \""
+ fieldError.getRejectedValue() + "\"")
.toList();
return errorResponse(request, HttpStatus.BAD_REQUEST, errorList.toString());
}
private String userReadableEntityClassName(final String exceptionMessage) { private String userReadableEntityClassName(final String exceptionMessage) {
final var regex = "(net.hostsharing.hsadminng.[a-z0-9_.]*.[A-Za-z0-9_$]*Entity) "; final var regex = "(net.hostsharing.hsadminng.[a-z0-9_.]*.[A-Za-z0-9_$]*Entity) ";
final var pattern = Pattern.compile(regex); final var pattern = Pattern.compile(regex);

View File

@ -13,7 +13,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.validation.Valid;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
@ -59,7 +58,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
public ResponseEntity<HsOfficeCoopAssetsTransactionResource> addCoopAssetsTransaction( public ResponseEntity<HsOfficeCoopAssetsTransactionResource> addCoopAssetsTransaction(
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
@Valid final HsOfficeCoopAssetsTransactionInsertResource requestBody) { final HsOfficeCoopAssetsTransactionInsertResource requestBody) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
validate(requestBody); validate(requestBody);

View File

@ -13,7 +13,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.validation.Valid;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
@ -60,7 +59,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
public ResponseEntity<HsOfficeCoopSharesTransactionResource> addCoopSharesTransaction( public ResponseEntity<HsOfficeCoopSharesTransactionResource> addCoopSharesTransaction(
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
@Valid final HsOfficeCoopSharesTransactionInsertResource requestBody) { final HsOfficeCoopSharesTransactionInsertResource requestBody) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
validate(requestBody); validate(requestBody);

View File

@ -12,7 +12,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import jakarta.validation.Valid;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@ -53,7 +52,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
public ResponseEntity<HsOfficeMembershipResource> addMembership( public ResponseEntity<HsOfficeMembershipResource> addMembership(
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
@Valid final HsOfficeMembershipInsertResource body) { final HsOfficeMembershipInsertResource body) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.hs.office.membership; package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.range.PostgreSQLRangeType; import io.hypersistence.utils.hibernate.type.range.PostgreSQLRangeType;
import com.vladmihalcea.hibernate.type.range.Range; import io.hypersistence.utils.hibernate.type.range.Range;
import lombok.*; import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;

View File

@ -14,7 +14,6 @@ import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBui
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import jakarta.validation.Valid;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@ -57,7 +56,7 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
public ResponseEntity<HsOfficeSepaMandateResource> addSepaMandate( public ResponseEntity<HsOfficeSepaMandateResource> addSepaMandate(
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
@Valid final HsOfficeSepaMandateInsertResource body) { final HsOfficeSepaMandateInsertResource body) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.hs.office.sepamandate; package net.hostsharing.hsadminng.hs.office.sepamandate;
import com.vladmihalcea.hibernate.type.range.PostgreSQLRangeType; import io.hypersistence.utils.hibernate.type.range.PostgreSQLRangeType;
import com.vladmihalcea.hibernate.type.range.Range; import io.hypersistence.utils.hibernate.type.range.Range;
import lombok.*; import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.mapper; package net.hostsharing.hsadminng.mapper;
import com.vladmihalcea.hibernate.type.range.Range; import io.hypersistence.utils.hibernate.type.range.Range;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import java.time.LocalDate; import java.time.LocalDate;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.membership; package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.range.Range; import io.hypersistence.utils.hibernate.type.range.Range;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.HsadminNgApplication;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.membership; package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.range.Range; import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeReasonForTerminationResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeReasonForTerminationResource;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.membership; package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.range.Range; import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.membership; package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.range.Range; import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.membership; package net.hostsharing.hsadminng.hs.office.membership;
import com.vladmihalcea.hibernate.type.range.Range; import io.hypersistence.utils.hibernate.type.range.Range;
import java.time.LocalDate; import java.time.LocalDate;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.sepamandate; package net.hostsharing.hsadminng.hs.office.sepamandate;
import com.vladmihalcea.hibernate.type.range.Range; import io.hypersistence.utils.hibernate.type.range.Range;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.HsadminNgApplication;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.sepamandate; package net.hostsharing.hsadminng.hs.office.sepamandate;
import com.vladmihalcea.hibernate.type.range.Range; import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandatePatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandatePatchResource;
import net.hostsharing.test.PatchUnitTestBase; import net.hostsharing.test.PatchUnitTestBase;

View File

@ -1,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.sepamandate; package net.hostsharing.hsadminng.hs.office.sepamandate;
import com.vladmihalcea.hibernate.type.range.Range; import io.hypersistence.utils.hibernate.type.range.Range;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;

View File

@ -7,7 +7,6 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -30,9 +29,7 @@ class PostgresArrayIntegrationTest {
return emptyArray; return emptyArray;
end; $$; end; $$;
""").executeUpdate(); """).executeUpdate();
final byte[] pgArray = (byte[]) em.createNativeQuery("SELECT returnEmptyArray()", String[].class).getSingleResult(); final String[] result = (String[]) em.createNativeQuery("SELECT returnEmptyArray()", String[].class).getSingleResult();
final String[] result = PostgresArray.fromPostgresArray(pgArray, String.class, Function.identity());
assertThat(result).isEmpty(); assertThat(result).isEmpty();
} }
@ -53,9 +50,7 @@ class PostgresArrayIntegrationTest {
return array[text1, text2, text3, null, text4]; return array[text1, text2, text3, null, text4];
end; $$; end; $$;
""").executeUpdate(); """).executeUpdate();
final byte[] pgArray = (byte[]) em.createNativeQuery("SELECT returnStringArray()", String[].class).getSingleResult(); final String[] result = (String[]) em.createNativeQuery("SELECT returnStringArray()", String[].class).getSingleResult();
final String[] result = PostgresArray.fromPostgresArray(pgArray, String.class, Function.identity());
assertThat(result).containsExactly("one", "two, three", "four; five", null, "say \"Hello\" to me"); assertThat(result).containsExactly("one", "two, three", "four; five", null, "say \"Hello\" to me");
} }
@ -75,9 +70,7 @@ class PostgresArrayIntegrationTest {
return ARRAY[uuid1, uuid2, null, uuid3]; return ARRAY[uuid1, uuid2, null, uuid3];
end; $$; end; $$;
""").executeUpdate(); """).executeUpdate();
final byte[] pgArray = (byte[]) em.createNativeQuery("SELECT returnUuidArray()", UUID[].class).getSingleResult(); final UUID[] result = (UUID[]) em.createNativeQuery("SELECT returnUuidArray()", UUID[].class).getSingleResult();
final UUID[] result = PostgresArray.fromPostgresArray(pgArray, UUID.class, UUID::fromString);
assertThat(result).containsExactly( assertThat(result).containsExactly(
UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479"), UUID.fromString("f47ac10b-58cc-4372-a567-0e02b2c3d479"),