From 7ee5f184a67cef527479f3c95e9e785ee4c5b29f Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 17:52:03 +0200 Subject: [PATCH 01/56] typo fix Histroy -> History --- .../dao/jdbc/{HistroyDaoJdbc.java => HistoryDaoJdbc.java} | 4 ++-- .../src/main/java/dev/dsf/fhir/spring/config/DaoConfig.java | 4 ++-- .../src/test/java/dev/dsf/fhir/dao/HistoryDaoTest.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/{HistroyDaoJdbc.java => HistoryDaoJdbc.java} (98%) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HistroyDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HistoryDaoJdbc.java similarity index 98% rename from dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HistroyDaoJdbc.java rename to dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HistoryDaoJdbc.java index 99915704c..9388874eb 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HistroyDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/HistoryDaoJdbc.java @@ -44,14 +44,14 @@ import dev.dsf.fhir.search.PageAndCount; import dev.dsf.fhir.search.SearchQueryParameter; -public class HistroyDaoJdbc implements HistoryDao, InitializingBean +public class HistoryDaoJdbc implements HistoryDao, InitializingBean { private final DataSource dataSource; private final FhirContext fhirContext; private final BinaryDaoJdbc binaryDao; private final PgObjectFactory pgObjectFactory; - public HistroyDaoJdbc(DataSource dataSource, FhirContext fhirContext, BinaryDaoJdbc binaryDao, + public HistoryDaoJdbc(DataSource dataSource, FhirContext fhirContext, BinaryDaoJdbc binaryDao, PgObjectFactory pgObjectFactory) { this.dataSource = dataSource; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/DaoConfig.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/DaoConfig.java index e680a9351..7180a0022 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/DaoConfig.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/DaoConfig.java @@ -64,7 +64,7 @@ import dev.dsf.fhir.dao.jdbc.EndpointDaoJdbc; import dev.dsf.fhir.dao.jdbc.GroupDaoJdbc; import dev.dsf.fhir.dao.jdbc.HealthcareServiceDaoJdbc; -import dev.dsf.fhir.dao.jdbc.HistroyDaoJdbc; +import dev.dsf.fhir.dao.jdbc.HistoryDaoJdbc; import dev.dsf.fhir.dao.jdbc.LibraryDaoJdbc; import dev.dsf.fhir.dao.jdbc.LocationDaoJdbc; import dev.dsf.fhir.dao.jdbc.MeasureDaoJdbc; @@ -357,7 +357,7 @@ public DaoProvider daoProvider() @Bean public HistoryDao historyDao() { - return new HistroyDaoJdbc(dataSource(), fhirConfig.fhirContext(), (BinaryDaoJdbc) binaryDao(), + return new HistoryDaoJdbc(dataSource(), fhirConfig.fhirContext(), (BinaryDaoJdbc) binaryDao(), new PgObjectFactoryImpl(fhirConfig.fhirContext(), jsonConfig.objectMapper())); } diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/HistoryDaoTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/HistoryDaoTest.java index 150528031..414b17e31 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/HistoryDaoTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/HistoryDaoTest.java @@ -43,7 +43,7 @@ import de.hsheilbronn.mi.utils.test.PostgreSqlContainerLiquibaseTemplateClassRule; import de.hsheilbronn.mi.utils.test.PostgresTemplateRule; import dev.dsf.fhir.dao.jdbc.BinaryDaoJdbc; -import dev.dsf.fhir.dao.jdbc.HistroyDaoJdbc; +import dev.dsf.fhir.dao.jdbc.HistoryDaoJdbc; import dev.dsf.fhir.dao.jdbc.OrganizationDaoJdbc; import dev.dsf.fhir.dao.jdbc.PgObjectFactoryImpl; import dev.dsf.fhir.history.AtParameter; @@ -92,7 +92,7 @@ public static void afterClass() throws Exception .disable(Feature.AUTO_CLOSE_TARGET).build(); private final OrganizationDao orgDao = new OrganizationDaoJdbc(defaultDataSource, permanentDeleteDataSource, fhirContext, objectMapper); - private final HistoryDao dao = new HistroyDaoJdbc(defaultDataSource, fhirContext, + private final HistoryDao dao = new HistoryDaoJdbc(defaultDataSource, fhirContext, new BinaryDaoJdbc(defaultDataSource, permanentDeleteDataSource, fhirContext, objectMapper, DATABASE_USERS_GROUP), new PgObjectFactoryImpl(fhirContext, objectMapper)); From 1e81611a7e3793f8a439e61e20c708058a270a0f Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 18:12:28 +0200 Subject: [PATCH 02/56] typo fix Resouce -> Resource, resouce -> resource --- .../bpe/api/plugin/AbstractProcessPlugin.java | 16 ++++++++-------- .../dev/dsf/fhir/dao/command/UpdateCommand.java | 5 +++-- ...FoundAndResourceVersionNoMatchException.java} | 2 +- .../java/dev/dsf/fhir/help/ExceptionHandler.java | 6 +++--- .../dev/dsf/fhir/help/ResponseGenerator.java | 2 +- .../impl/AbstractResourceServiceImpl.java | 2 +- .../dsf/fhir/dao/AbstractReadAccessDaoTest.java | 4 ++-- .../dsf/fhir/dao/AbstractResourceDaoTest.java | 6 +++--- .../AbstractResourceServiceSecureTest.java | 12 ++++++------ .../secure/ResourceServiceSecureTest.java | 4 ++-- 10 files changed, 30 insertions(+), 29 deletions(-) rename dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/function/{SupplierWithSqlAndResourceNotFoundAndResouceVersionNoMatchException.java => SupplierWithSqlAndResourceNotFoundAndResourceVersionNoMatchException.java} (91%) diff --git a/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java b/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java index d7bbf3b40..d0b166e9b 100644 --- a/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java +++ b/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java @@ -1113,7 +1113,7 @@ else if (fhirConfig.isValueSet(resource)) }; } - private boolean isValidMetadataResouce(Object resource, String file) + private boolean isValidMetadataResource(Object resource, String file) { boolean urlOk = fhirConfig.hasMetadataResourceUrl(resource); boolean versionDefined = fhirConfig.hasMetadataresourceVersion(resource); @@ -1158,7 +1158,7 @@ private boolean hasProfile(String file, Object resource, String profile) private boolean isValidActivityDefinition(String file, Object resource) { boolean hasProfile = hasProfile(file, resource, P_ACTIVITY_DEFINITION); - boolean metadataResourceOk = isValidMetadataResouce(resource, file); + boolean metadataResourceOk = isValidMetadataResource(resource, file); boolean urlOk = fhirConfig.getActivityDefinitionUrl(resource) .map(u -> ACTIVITY_DEFINITION_URL_PATTERN.matcher(u).matches()).orElse(false); @@ -1173,17 +1173,17 @@ private boolean isValidActivityDefinition(String file, Object resource) private boolean isValidCodeSystem(String file, Object resource) { - return hasProfile(file, resource, P_CODE_SYSTEM) && isValidMetadataResouce(resource, file); + return hasProfile(file, resource, P_CODE_SYSTEM) && isValidMetadataResource(resource, file); } private boolean isValidLibrary(String file, Object resource) { - return hasProfile(file, resource, P_LIBRARY) && isValidMetadataResouce(resource, file); + return hasProfile(file, resource, P_LIBRARY) && isValidMetadataResource(resource, file); } private boolean isValidMeasure(String file, Object resource) { - return hasProfile(file, resource, P_MEASURE) && isValidMetadataResouce(resource, file); + return hasProfile(file, resource, P_MEASURE) && isValidMetadataResource(resource, file); } private boolean isValidNamingSystem(String file, Object resource) @@ -1211,7 +1211,7 @@ private boolean isValidQuestionnaire(String file, Object resource) file, getDefinitionName(), getDefinitionVersion()); } - return hasProfile && hasQuestionnaireItemsWithRequired && isValidMetadataResouce(resource, file); + return hasProfile && hasQuestionnaireItemsWithRequired && isValidMetadataResource(resource, file); } private boolean isValidStructureDefinition(String file, Object resource) @@ -1237,7 +1237,7 @@ private boolean isValidStructureDefinition(String file, Object resource) } return hasProfile && hasStructureDefinitionTaskDsfValueSetBindingsWithoutVersion && baseDefinitionOk - && isValidMetadataResouce(resource, file); + && isValidMetadataResource(resource, file); } private boolean isValidTask(String file, Object resource, String localOrganizationIdentifierValue) @@ -1378,7 +1378,7 @@ private boolean isLocalOrganization(Reference reference, String refLocation, Str private boolean isValidValueSet(String file, Object resource) { - return hasProfile(file, resource, P_VALUE_SET) && isValidMetadataResouce(resource, file); + return hasProfile(file, resource, P_VALUE_SET) && isValidMetadataResource(resource, file); } private List filterBpmnModelsWithoutMatchingActivityDefinitions( diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/UpdateCommand.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/UpdateCommand.java index 4a840eb17..104367211 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/UpdateCommand.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/UpdateCommand.java @@ -119,7 +119,7 @@ public void preExecute(Map idTranslationTable, Connection connec } else if (!resource.hasIdElement() || !resource.getIdElement().hasIdPart()) { - Response response = responseGenerator.bundleEntryResouceMissingId(index, + Response response = responseGenerator.bundleEntryResourceMissingId(index, resource.getResourceType().name()); throw new WebApplicationException(response); } @@ -337,7 +337,8 @@ private void updateById(Map idTranslationTable, LargeObjectManag Optional ifMatch = Optional.ofNullable(entry.getRequest().getIfMatch()) .flatMap(parameterConverter::toEntityTag).flatMap(parameterConverter::toVersion); - updatedResource = exceptionHandler.handleSqlExAndResourceNotFoundExAndResouceVersionNonMatchEx(resourceTypeName, + updatedResource = exceptionHandler.handleSqlExAndResourceNotFoundExAndResourceVersionNonMatchEx( + resourceTypeName, () -> updateWithTransaction(largeObjectManager, connection, resource, ifMatch.orElse(null))); } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/function/SupplierWithSqlAndResourceNotFoundAndResouceVersionNoMatchException.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/function/SupplierWithSqlAndResourceNotFoundAndResourceVersionNoMatchException.java similarity index 91% rename from dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/function/SupplierWithSqlAndResourceNotFoundAndResouceVersionNoMatchException.java rename to dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/function/SupplierWithSqlAndResourceNotFoundAndResourceVersionNoMatchException.java index 1b9d72a04..a441c8d61 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/function/SupplierWithSqlAndResourceNotFoundAndResouceVersionNoMatchException.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/function/SupplierWithSqlAndResourceNotFoundAndResourceVersionNoMatchException.java @@ -21,7 +21,7 @@ import dev.dsf.fhir.dao.exception.ResourceVersionNoMatchException; @FunctionalInterface -public interface SupplierWithSqlAndResourceNotFoundAndResouceVersionNoMatchException +public interface SupplierWithSqlAndResourceNotFoundAndResourceVersionNoMatchException { R get() throws SQLException, ResourceNotFoundException, ResourceVersionNoMatchException; } \ No newline at end of file diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ExceptionHandler.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ExceptionHandler.java index 68c126ffd..4c3612ca5 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ExceptionHandler.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ExceptionHandler.java @@ -39,7 +39,7 @@ import dev.dsf.fhir.function.RunnableWithSqlException; import dev.dsf.fhir.function.RunnableWithSqlResourceNotFoundAndResourceNotMarkedDeletedException; import dev.dsf.fhir.function.SupplierWithSqlAndResourceDeletedException; -import dev.dsf.fhir.function.SupplierWithSqlAndResourceNotFoundAndResouceVersionNoMatchException; +import dev.dsf.fhir.function.SupplierWithSqlAndResourceNotFoundAndResourceVersionNoMatchException; import dev.dsf.fhir.function.SupplierWithSqlAndResourceNotFoundException; import dev.dsf.fhir.function.SupplierWithSqlException; import jakarta.ws.rs.WebApplicationException; @@ -132,8 +132,8 @@ public WebApplicationException internalServerErrorBundleBatch(Exception e) return new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(outcome).build()); } - public T handleSqlExAndResourceNotFoundExAndResouceVersionNonMatchEx(String resourceTypeName, - SupplierWithSqlAndResourceNotFoundAndResouceVersionNoMatchException s) + public T handleSqlExAndResourceNotFoundExAndResourceVersionNonMatchEx(String resourceTypeName, + SupplierWithSqlAndResourceNotFoundAndResourceVersionNoMatchException s) { try { diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ResponseGenerator.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ResponseGenerator.java index 4dfa782c3..c35a83e8c 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ResponseGenerator.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ResponseGenerator.java @@ -899,7 +899,7 @@ public Response unsupportedConditionalUpdateQuery(int bundleIndex, String query, } - public Response bundleEntryResouceMissingId(int bundleIndex, String resourceTypeName) + public Response bundleEntryResourceMissingId(int bundleIndex, String resourceTypeName) { logger.warn("Bundle entry of type {} at bundle index {} is missing id value", resourceTypeName, bundleIndex); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java index f14270501..6fd7d0b5f 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java @@ -580,7 +580,7 @@ public Response update(String id, R resource, UriInfo uri, HttpHeaders headers) .flatMap(parameterConverter::toEntityTag).flatMap(parameterConverter::toVersion); R updatedResource = exceptionHandler - .handleSqlExAndResourceNotFoundExAndResouceVersionNonMatchEx(resourceTypeName, () -> + .handleSqlExAndResourceNotFoundExAndResourceVersionNonMatchEx(resourceTypeName, () -> { try (Connection connection = dao.newReadWriteTransaction()) { diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/AbstractReadAccessDaoTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/AbstractReadAccessDaoTest.java index d99622a37..803bda56e 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/AbstractReadAccessDaoTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/AbstractReadAccessDaoTest.java @@ -58,10 +58,10 @@ public abstract class AbstractReadAccessDaoTest resouceClass, + protected AbstractReadAccessDaoTest(Class resourceClass, QuadFunction daoCreator) { - super(resouceClass, daoCreator); + super(resourceClass, daoCreator); } protected void assertReadAccessEntryCount(int totalExpectedCount, int expectedCount, Resource resource, diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/AbstractResourceDaoTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/AbstractResourceDaoTest.java index 1490bfcba..5bcb24748 100755 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/AbstractResourceDaoTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/AbstractResourceDaoTest.java @@ -98,7 +98,7 @@ public static void afterClass() throws Exception permanentDeleteDataSource.unwrap(BasicDataSource.class).close(); } - protected final Class resouceClass; + protected final Class resourceClass; protected final QuadFunction daoCreator; protected final FhirContext fhirContext = FhirContext.forR4(); @@ -108,10 +108,10 @@ public static void afterClass() throws Exception .disable(Feature.AUTO_CLOSE_TARGET).build(); protected C dao; - protected AbstractResourceDaoTest(Class resouceClass, + protected AbstractResourceDaoTest(Class resourceClass, QuadFunction daoCreator) { - this.resouceClass = resouceClass; + this.resourceClass = resourceClass; this.daoCreator = daoCreator; } diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecureTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecureTest.java index 01b31e1ca..b7f303051 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecureTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecureTest.java @@ -93,7 +93,7 @@ S create(S delegate, String serverBase, ResponseGenerator responseGenerator, protected final Class resourceClass; protected final Class serviceClass; protected final Class daoClass; - protected final Supplier resouceSupplier; + protected final Supplier resourceSupplier; protected final ResourceServiceSecureFactory resourceServiceSecureFactory; protected final ValidationRules validationRules = new ValidationRules(SERVER_BASE); @@ -115,12 +115,12 @@ S create(S delegate, String serverBase, ResponseGenerator responseGenerator, protected S resourceServiceSecure; public AbstractResourceServiceSecureTest(Class resourceClass, Class serviceClass, Class daoClass, - Supplier resouceSupplier, ResourceServiceSecureFactory resourceServiceSecureFactory) + Supplier resourceSupplier, ResourceServiceSecureFactory resourceServiceSecureFactory) { this.resourceClass = resourceClass; this.serviceClass = serviceClass; this.daoClass = daoClass; - this.resouceSupplier = resouceSupplier; + this.resourceSupplier = resourceSupplier; this.resourceServiceSecureFactory = resourceServiceSecureFactory; delegate = mock(serviceClass); @@ -175,7 +175,7 @@ protected final S createResourceServiceSecure() protected final R createResource() { - return resouceSupplier.get(); + return resourceSupplier.get(); } protected final R createResourceWithIdAndVersion() @@ -448,7 +448,7 @@ public void expectCreatedCreateAllowed() throws Exception } @Test - public void expectForbiddenCreateAllowedNonValidResouce() throws Exception + public void expectForbiddenCreateAllowedNonValidResource() throws Exception { R resource = createResourceWithIdAndVersion(); @@ -516,7 +516,7 @@ public void historyBaseMustEnforceHistoryAuthorization() } @Test - public void historyResouceMustEnforceHistoryAuthorization() + public void historyResourceMustEnforceHistoryAuthorization() { when(delegate.history(anyString(), any(), any())).thenReturn(mock(Response.class)); diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/webservice/secure/ResourceServiceSecureTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/webservice/secure/ResourceServiceSecureTest.java index 8235ddc7c..faae37751 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/webservice/secure/ResourceServiceSecureTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/webservice/secure/ResourceServiceSecureTest.java @@ -198,9 +198,9 @@ public static Collection data() public ResourceServiceSecureTest(String label, Class resourceClass, Class> serviceClass, Class> daoClass, - Supplier resouceSupplier, + Supplier resourceSupplier, ResourceServiceSecureFactory, ResourceDao> resourceServiceSecureFactory) { - super(resourceClass, serviceClass, daoClass, resouceSupplier, resourceServiceSecureFactory); + super(resourceClass, serviceClass, daoClass, resourceSupplier, resourceServiceSecureFactory); } } From b6236c31cc096b82caa488e00b989d79c8f59608 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 18:16:27 +0200 Subject: [PATCH 03/56] typo fix Siging -> Signing --- .../dev/dsf/bpe/spring/config/MailConfig.java | 10 +++++----- .../bpe/spring/config/PropertiesConfig.java | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/MailConfig.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/MailConfig.java index db59cef94..3e7bf5d94 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/MailConfig.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/MailConfig.java @@ -88,11 +88,11 @@ private BpeMailService newSmptMailService() KeyStore trustStore = propertiesConfig.getMailServerTrustStore(); char[] keyStorePassword = UUID.randomUUID().toString().toCharArray(); KeyStore keyStore = propertiesConfig.getMailClientKeyStore(keyStorePassword); - KeyStore signStore = propertiesConfig.getMailSmimeSigingKeyStore(); + KeyStore signStore = propertiesConfig.getMailSmimeSigningKeyStore(); return new SmtpMailService(fromAddress, toAddresses, toAddressesCc, replyToAddresses, useSmtps, mailServerHostname, mailServerPort, mailServerUsername, mailServerPassword, trustStore, keyStore, - keyStorePassword, signStore, propertiesConfig.getMailSmimeSigingKeyStorePassword(), + keyStorePassword, signStore, propertiesConfig.getMailSmimeSigningKeyStorePassword(), propertiesConfig.getSendMailOnErrorLogEvent(), propertiesConfig.getMailOnErrorLogEventBufferSize(), propertiesConfig.getMailOnErrorLogEventDebugLogLocation()); } @@ -106,7 +106,7 @@ public void afterPropertiesSet() throws Exception "Mail client config: {fromAddress: {}, toAddresses: {}, toAddressesCc: {}, replyToAddresses: {}," + " useSmtps: {}, mailServerHostname: {}, mailServerPort: {}, mailServerUsername: {}," + " mailServerPassword: {}, trustStore: {}, clientCertificate: {}, clientCertificatePrivateKey: {}," - + " clientCertificatePrivateKeyPassword: {}, smimeSigingKeyStore: {}, smimeSigingKeyStorePassword: {}," + + " clientCertificatePrivateKeyPassword: {}, smimeSigningKeyStore: {}, smimeSigningKeyStorePassword: {}," + " sendTestMailOnStartup: {}, sendMailOnErrorLogEvent: {}, mailOnErrorLogEventBufferSize: {}," + " mailOnErrorLogEventDebugLogLocation: {}}", propertiesConfig.getMailFromAddress(), propertiesConfig.getMailToAddresses(), @@ -118,8 +118,8 @@ public void afterPropertiesSet() throws Exception propertiesConfig.getMailClientCertificateFile(), propertiesConfig.getMailClientCertificatePrivateKeyFile(), propertiesConfig.getMailClientCertificatePrivateKeyFilePassword() != null ? "***" : "null", - propertiesConfig.getMailSmimeSigingKeyStoreFile(), - propertiesConfig.getMailSmimeSigingKeyStorePassword() != null ? "***" : "null", + propertiesConfig.getMailSmimeSigningKeyStoreFile(), + propertiesConfig.getMailSmimeSigningKeyStorePassword() != null ? "***" : "null", propertiesConfig.getSendTestMailOnStartup(), propertiesConfig.getSendMailOnErrorLogEvent(), propertiesConfig.getMailOnErrorLogEventBufferSize(), propertiesConfig.getMailOnErrorLogEventDebugLogLocation()); diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java index 9965e151f..a95ac98e4 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java @@ -330,11 +330,11 @@ public class PropertiesConfig extends AbstractCertificateConfig implements Initi @Documentation(description = "PKCS12 encoded file with S/MIME certificate, private key and certificate chain to enable send mails to be S/MIME signed", recommendation = "Use docker secret file to configure", example = "/run/secrets/smime_certificate.p12") @Value("${dev.dsf.bpe.mail.smime.p12Keystore:#{null}}") - private String mailSmimeSigingKeyStoreFile; + private String mailSmimeSigningKeyStoreFile; @Documentation(description = "Password to decrypt the PKCS12 encoded S/MIMIE certificate file", recommendation = "Use docker secret file to configure using *${env_variable}_FILE*", example = "/run/secrets/smime_certificate.p12.password") @Value("${dev.dsf.bpe.mail.smime.p12Keystore.password:#{null}}") - private char[] mailSmimeSigingKeyStorePassword; + private char[] mailSmimeSigningKeyStorePassword; @Documentation(description = "To enable a test mail being send on startup of the BPE, set to `true`; requires SMTP server to be configured") @Value("${dev.dsf.bpe.mail.sendTestMailOnStartup:false}") @@ -910,23 +910,23 @@ public KeyStore getMailClientKeyStore(char[] keyStorePassword) "dev.dsf.bpe.mail.client.certificate", "dev.dsf.bpe.mail.client.certificate.private.key"); } - public String getMailSmimeSigingKeyStoreFile() + public String getMailSmimeSigningKeyStoreFile() { - return mailSmimeSigingKeyStoreFile; + return mailSmimeSigningKeyStoreFile; } - public char[] getMailSmimeSigingKeyStorePassword() + public char[] getMailSmimeSigningKeyStorePassword() { - return mailSmimeSigingKeyStorePassword; + return mailSmimeSigningKeyStorePassword; } @Bean - public KeyStore getMailSmimeSigingKeyStore() + public KeyStore getMailSmimeSigningKeyStore() { - if (getMailSmimeSigingKeyStoreFile() == null) + if (getMailSmimeSigningKeyStoreFile() == null) return null; else - return createKeyStoreFromP12(getMailSmimeSigingKeyStoreFile(), getMailSmimeSigingKeyStorePassword(), + return createKeyStoreFromP12(getMailSmimeSigningKeyStoreFile(), getMailSmimeSigningKeyStorePassword(), "dev.dsf.bpe.mail.smime.p12Keystore"); } From dccdc5c5d408cb340d103da01a7d7745b6c94fda Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 18:18:39 +0200 Subject: [PATCH 04/56] typo fix Abbort -> Abort --- .../java/dev/dsf/fhir/dao/command/AbstractCommandList.java | 2 +- .../dev/dsf/fhir/dao/command/TransactionCommandList.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/AbstractCommandList.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/AbstractCommandList.java index b15ef23a0..46f052c47 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/AbstractCommandList.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/AbstractCommandList.java @@ -103,7 +103,7 @@ else if (command instanceof ReadCommand) } } - protected void auditLogAbbort(Command command) + protected void auditLogAbort(Command command) { if (command instanceof DeleteCommand) { diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/TransactionCommandList.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/TransactionCommandList.java index fd520f394..850ba01c0 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/TransactionCommandList.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/TransactionCommandList.java @@ -111,7 +111,7 @@ public Bundle execute() throws WebApplicationException try { - commands.stream().limit(c.getIndex()).forEach(this::auditLogAbbort); + commands.stream().limit(c.getIndex()).forEach(this::auditLogAbort); auditLogResult(c, toEntry(e)); } catch (Exception e1) @@ -153,7 +153,7 @@ public Bundle execute() throws WebApplicationException try { - commands.stream().limit(c.getIndex()).forEach(this::auditLogAbbort); + commands.stream().limit(c.getIndex()).forEach(this::auditLogAbort); auditLogResult(c, toEntry(e)); } catch (Exception e1) @@ -193,7 +193,7 @@ public Bundle execute() throws WebApplicationException try { - commands.stream().limit(c.getIndex()).forEach(this::auditLogAbbort); + commands.stream().limit(c.getIndex()).forEach(this::auditLogAbort); auditLogResult(c, toEntry(e)); } catch (Exception e1) From 86a5132d0c0343d18b34f28e5b62f0fc3c201159 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 18:20:33 +0200 Subject: [PATCH 05/56] typo fix reslove -> resolve --- .../src/main/java/dev/dsf/fhir/search/Matcher.java | 2 +- .../src/main/java/dev/dsf/fhir/search/SearchQuery.java | 2 +- .../dsf/fhir/subscription/WebSocketSubscriptionManagerImpl.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/Matcher.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/Matcher.java index 75b4c8867..778d450d3 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/Matcher.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/Matcher.java @@ -23,7 +23,7 @@ public interface Matcher { - void resloveReferencesForMatching(Resource resource, DaoProvider daoProvider) throws SQLException; + void resolveReferencesForMatching(Resource resource, DaoProvider daoProvider) throws SQLException; boolean matches(Resource resource); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java index 56a0f274e..073f90eb3 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java @@ -512,7 +512,7 @@ public Class getResourceType() } @Override - public void resloveReferencesForMatching(Resource resource, DaoProvider daoProvider) throws SQLException + public void resolveReferencesForMatching(Resource resource, DaoProvider daoProvider) throws SQLException { if (resource == null || !getResourceType().isInstance(resource)) return; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/subscription/WebSocketSubscriptionManagerImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/subscription/WebSocketSubscriptionManagerImpl.java index d64333c99..db9149c8c 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/subscription/WebSocketSubscriptionManagerImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/subscription/WebSocketSubscriptionManagerImpl.java @@ -78,7 +78,7 @@ boolean matches(Resource resource, DaoProvider daoProvider) { try { - matcher.resloveReferencesForMatching(resource, daoProvider); + matcher.resolveReferencesForMatching(resource, daoProvider); } catch (SQLException e) { From c5b82625ad151ab9caa0a021d1c232a922fa0ce2 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 18:35:50 +0200 Subject: [PATCH 06/56] typo fix crate, crete -> create --- .../src/main/java/dev/dsf/fhir/help/ResponseGenerator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ResponseGenerator.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ResponseGenerator.java index c35a83e8c..141cd97c8 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ResponseGenerator.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ResponseGenerator.java @@ -778,10 +778,10 @@ public Response badDeleteRequestUrl(int bundleIndex, String url) public Response badCreateRequestUrl(int bundleIndex, String url) { - logger.warn("Bad crate request url {} at bundle index {}", url, bundleIndex); + logger.warn("Bad create request url {} at bundle index {}", url, bundleIndex); OperationOutcome outcome = createOutcome(IssueSeverity.ERROR, IssueType.PROCESSING, - "Bad crete request url " + url + " at bundle index " + bundleIndex); + "Bad create request url " + url + " at bundle index " + bundleIndex); return Response.status(Status.BAD_REQUEST).entity(outcome).build(); } From 702be4347cd38fd4a1587655dbe30bcc2a545d4a Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 18:40:40 +0200 Subject: [PATCH 07/56] typo fix snaphost -> snapshot --- .../fhir/dao/command/DeleteStructureDefinitionCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/DeleteStructureDefinitionCommand.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/DeleteStructureDefinitionCommand.java index 4a2571f5f..fd3e430d7 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/DeleteStructureDefinitionCommand.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/DeleteStructureDefinitionCommand.java @@ -64,10 +64,10 @@ protected boolean deleteWithTransaction(ResourceDao dao, Connection connectio } catch (SQLException | ResourceNotFoundException e) { - logger.debug("Error while deleting StructureDefinition snaphost for id {}, exception will be ignored", + logger.debug("Error while deleting StructureDefinition snapshot for id {}, exception will be ignored", uuid.toString(), e); logger.warn( - "Error while deleting StructureDefinition snaphost for id {}, exception will be ignored: {} - {}", + "Error while deleting StructureDefinition snapshot for id {}, exception will be ignored: {} - {}", uuid.toString(), e.getClass().getName(), e.getMessage()); } From 6a29545053b49210f79d75be9594dfcf37e86010 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 18:58:50 +0200 Subject: [PATCH 08/56] fixed wrong class names in log messages --- .../fhir/subscription/WebSocketSubscriptionManagerImpl.java | 3 ++- .../src/main/java/dev/dsf/fhir/websocket/ServerEndpoint.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/subscription/WebSocketSubscriptionManagerImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/subscription/WebSocketSubscriptionManagerImpl.java index db9149c8c..87855b525 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/subscription/WebSocketSubscriptionManagerImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/subscription/WebSocketSubscriptionManagerImpl.java @@ -234,7 +234,8 @@ public void destroy() throws Exception { executor.shutdownNow(); if (!executor.awaitTermination(60, TimeUnit.SECONDS)) - logger.warn("EventManager executor did not terminate"); + logger.warn("{} executor did not terminate", + WebSocketSubscriptionManagerImpl.class.getSimpleName()); } } catch (InterruptedException ie) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/websocket/ServerEndpoint.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/websocket/ServerEndpoint.java index ab44d7cd7..01d60b182 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/websocket/ServerEndpoint.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/websocket/ServerEndpoint.java @@ -221,7 +221,7 @@ public void destroy() throws Exception { scheduler.shutdownNow(); if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) - logger.warn("EventEndpoint scheduler did not terminate"); + logger.warn("{} scheduler did not terminate", ServerEndpoint.class.getSimpleName()); } } catch (InterruptedException ie) From 79c3b2c83a7818581ae0c9d8230848e97a72d54e Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 19:18:18 +0200 Subject: [PATCH 09/56] added missing final modifier to static fields --- .../dev/dsf/common/ui/webservice/StaticResourcesService.java | 4 ++-- .../fhir/search/filter/DocumentReferenceIdentityFilter.java | 2 +- .../fhir/search/filter/PractitionerRoleIdentityFilter.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dsf-common/dsf-common-ui/src/main/java/dev/dsf/common/ui/webservice/StaticResourcesService.java b/dsf-common/dsf-common-ui/src/main/java/dev/dsf/common/ui/webservice/StaticResourcesService.java index 5f188d3eb..9f0b41e17 100644 --- a/dsf-common/dsf-common-ui/src/main/java/dev/dsf/common/ui/webservice/StaticResourcesService.java +++ b/dsf-common/dsf-common-ui/src/main/java/dev/dsf/common/ui/webservice/StaticResourcesService.java @@ -52,8 +52,8 @@ public class StaticResourcesService private static final java.nio.file.Path OVERRIDE_RESOURCE_FOLDER = Paths.get("ui"); - private static CacheControl NO_TRANSFORM = new CacheControl(); - private static CacheControl NO_CACHE_NO_TRANSFORM = new CacheControl(); + private static final CacheControl NO_TRANSFORM = new CacheControl(); + private static final CacheControl NO_CACHE_NO_TRANSFORM = new CacheControl(); static { // no-transform set by default diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/filter/DocumentReferenceIdentityFilter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/filter/DocumentReferenceIdentityFilter.java index ad52d3e47..50f4e9c5a 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/filter/DocumentReferenceIdentityFilter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/filter/DocumentReferenceIdentityFilter.java @@ -27,7 +27,7 @@ public class DocumentReferenceIdentityFilter extends AbstractMetaTagAuthorizatio private static final FhirServerRole READ_ROLE = FhirServerRoleImpl.read(ResourceType.DocumentReference); private static final String RESOURCE_TABLE = "current_document_references"; - private static String RESOURCE_ID_COLUMN = "document_reference_id"; + private static final String RESOURCE_ID_COLUMN = "document_reference_id"; public DocumentReferenceIdentityFilter(Identity identity) { diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/filter/PractitionerRoleIdentityFilter.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/filter/PractitionerRoleIdentityFilter.java index cc097d2bd..fba15419b 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/filter/PractitionerRoleIdentityFilter.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/filter/PractitionerRoleIdentityFilter.java @@ -27,7 +27,7 @@ public class PractitionerRoleIdentityFilter extends AbstractMetaTagAuthorization private static final FhirServerRole READ_ROLE = FhirServerRoleImpl.read(ResourceType.PractitionerRole); private static final String RESOURCE_TABLE = "current_practitioner_roles"; - private static String RESOURCE_ID_COLUMN = "practitioner_role_id"; + private static final String RESOURCE_ID_COLUMN = "practitioner_role_id"; public PractitionerRoleIdentityFilter(Identity identity) { From f2fe569729820e3c08ff99115bd1ab63cfbadf9a Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 19:29:43 +0200 Subject: [PATCH 10/56] typo fix ... -> components componentes -> components cComponentes -> components eruComponentes -> components --- .../subscription/LocalFhirConnectorImpl.java | 4 ++-- .../SubscriptionAuthorizationRule.java | 13 +++++----- .../dsf/fhir/dao/command/CreateCommand.java | 10 ++++---- .../dsf/fhir/dao/command/DeleteCommand.java | 18 +++++++------- .../dev/dsf/fhir/dao/command/ReadCommand.java | 24 +++++++++---------- .../dsf/fhir/dao/command/UpdateCommand.java | 16 ++++++------- .../dsf/fhir/subscription/MatcherFactory.java | 6 ++--- .../impl/AbstractResourceServiceImpl.java | 6 ++--- 8 files changed, 48 insertions(+), 49 deletions(-) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/subscription/LocalFhirConnectorImpl.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/subscription/LocalFhirConnectorImpl.java index 5c0b90fa0..bf42c99b1 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/subscription/LocalFhirConnectorImpl.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/subscription/LocalFhirConnectorImpl.java @@ -81,10 +81,10 @@ private Map> parse(String queryParameters, String expectedP } else { - UriComponents componentes = UriComponentsBuilder + UriComponents components = UriComponentsBuilder .fromUriString(queryParameters.startsWith("?") ? queryParameters : "?" + queryParameters).build(); - return componentes.getQueryParams(); + return components.getQueryParams(); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/SubscriptionAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/SubscriptionAuthorizationRule.java index 2f74b6199..61c0a7b9d 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/SubscriptionAuthorizationRule.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/SubscriptionAuthorizationRule.java @@ -94,14 +94,14 @@ private Optional newResourceOk(Connection connection, Subscription newRe if (newResource.hasCriteria()) { - UriComponents cComponentes = UriComponentsBuilder.fromUriString(newResource.getCriteria()).build(); - if (cComponentes.getPathSegments().size() == 1) + UriComponents components = UriComponentsBuilder.fromUriString(newResource.getCriteria()).build(); + if (components.getPathSegments().size() == 1) { - Optional> optDao = daoProvider.getDao(cComponentes.getPathSegments().get(0)); + Optional> optDao = daoProvider.getDao(components.getPathSegments().get(0)); if (optDao.isPresent()) { SearchQuery searchQuery = optDao.get().createSearchQueryWithoutUserFilter(PageAndCount.exists()) - .configureParameters(cComponentes.getQueryParams()); + .configureParameters(components.getQueryParams()); List uQp = searchQuery.getUnsupportedQueryParameters(); if (!uQp.isEmpty()) { @@ -112,13 +112,12 @@ private Optional newResourceOk(Connection connection, Subscription newRe } else { - errors.add( - "Subscription.criteria invalid (resource '" + cComponentes.getPath() + "' not supported)"); + errors.add("Subscription.criteria invalid (resource '" + components.getPath() + "' not supported)"); } } else { - errors.add("Subscription.criteria invalid ('" + cComponentes.getPath() + "')"); + errors.add("Subscription.criteria invalid ('" + components.getPath() + "')"); } } else diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/CreateCommand.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/CreateCommand.java index 61e820519..3a95d008b 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/CreateCommand.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/CreateCommand.java @@ -105,10 +105,10 @@ public CreateCommand(int index, Identity identity, PreferReturnType returnType, public void preExecute(Map idTranslationTable, Connection connection, ValidationHelper validationHelper, SnapshotGenerator snapshotGenerator) { - UriComponents eruComponentes = UriComponentsBuilder.fromUriString(entry.getRequest().getUrl()).build(); + UriComponents components = UriComponentsBuilder.fromUriString(entry.getRequest().getUrl()).build(); // check standard create request url: e.g. Patient - if (eruComponentes.getPathSegments().size() == 1 && eruComponentes.getQueryParams().isEmpty()) + if (components.getPathSegments().size() == 1 && components.getQueryParams().isEmpty()) { if (!entry.hasFullUrl() || !entry.getFullUrl().startsWith(URL_UUID_PREFIX)) { @@ -226,8 +226,8 @@ private Optional checkAlreadyExists(Connection connection, String ifNo if (!ifNoneExist.contains("?")) ifNoneExist = '?' + ifNoneExist; - UriComponents componentes = UriComponentsBuilder.fromUriString(ifNoneExist).build(); - String path = componentes.getPath(); + UriComponents components = UriComponentsBuilder.fromUriString(ifNoneExist).build(); + String path = components.getPath(); if (path != null && !path.isBlank()) { Response response = responseGenerator.badIfNoneExistHeaderValue("no resource", ifNoneExist); @@ -235,7 +235,7 @@ private Optional checkAlreadyExists(Connection connection, String ifNo } Map> queryParameters = parameterConverter - .urlDecodeQueryParameters(componentes.getQueryParams()); + .urlDecodeQueryParameters(components.getQueryParams()); if (Arrays.stream(SearchQuery.STANDARD_PARAMETERS).anyMatch(queryParameters::containsKey)) { logger.warn( diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/DeleteCommand.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/DeleteCommand.java index ab5810406..5d3c344cb 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/DeleteCommand.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/DeleteCommand.java @@ -88,15 +88,15 @@ public DeleteCommand(int index, Identity identity, PreferReturnType returnType, public void execute(Map idTranslationTable, LargeObjectManager largeObjectManager, Connection connection, ValidationHelper validationHelper) throws SQLException, WebApplicationException { - UriComponents componentes = UriComponentsBuilder.fromUriString(entry.getRequest().getUrl()).build(); - resourceTypeName = componentes.getPathSegments().get(0); - - if (componentes.getPathSegments().size() == 2 && componentes.getQueryParams().isEmpty()) - deleteById(idTranslationTable, connection, componentes.getPathSegments().get(0), - componentes.getPathSegments().get(1)); - else if (componentes.getPathSegments().size() == 1 && !componentes.getQueryParams().isEmpty()) - deleteByCondition(idTranslationTable, connection, componentes.getPathSegments().get(0), - parameterConverter.urlDecodeQueryParameters(componentes.getQueryParams())); + UriComponents components = UriComponentsBuilder.fromUriString(entry.getRequest().getUrl()).build(); + resourceTypeName = components.getPathSegments().get(0); + + if (components.getPathSegments().size() == 2 && components.getQueryParams().isEmpty()) + deleteById(idTranslationTable, connection, components.getPathSegments().get(0), + components.getPathSegments().get(1)); + else if (components.getPathSegments().size() == 1 && !components.getQueryParams().isEmpty()) + deleteByCondition(idTranslationTable, connection, components.getPathSegments().get(0), + parameterConverter.urlDecodeQueryParameters(components.getQueryParams())); else { Response response = responseGenerator.badDeleteRequestUrl(index, entry.getRequest().getUrl()); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReadCommand.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReadCommand.java index cae27031f..5a36b8386 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReadCommand.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReadCommand.java @@ -108,19 +108,19 @@ public void execute(Map idTranslationTable, LargeObjectManager l if (requestUrl.startsWith(URL_UUID_PREFIX)) requestUrl = idTranslationTable.getOrDefault(requestUrl, new IdType(requestUrl)).getValue(); - UriComponents componentes = UriComponentsBuilder.fromUriString(requestUrl).build(); - resourceTypeName = componentes.getPathSegments().get(0); - - if (componentes.getPathSegments().size() == 2 && componentes.getQueryParams().isEmpty()) - readById(connection, resourceTypeName, componentes.getPathSegments().get(1)); - else if (componentes.getPathSegments().size() == 4 - && Constants.PARAM_HISTORY.equals(componentes.getPathSegments().get(2)) - && componentes.getQueryParams().isEmpty()) - readByIdAndVersion(connection, resourceTypeName, componentes.getPathSegments().get(1), - componentes.getPathSegments().get(3)); - else if (componentes.getPathSegments().size() == 1 && !componentes.getQueryParams().isEmpty()) + UriComponents components = UriComponentsBuilder.fromUriString(requestUrl).build(); + resourceTypeName = components.getPathSegments().get(0); + + if (components.getPathSegments().size() == 2 && components.getQueryParams().isEmpty()) + readById(connection, resourceTypeName, components.getPathSegments().get(1)); + else if (components.getPathSegments().size() == 4 + && Constants.PARAM_HISTORY.equals(components.getPathSegments().get(2)) + && components.getQueryParams().isEmpty()) + readByIdAndVersion(connection, resourceTypeName, components.getPathSegments().get(1), + components.getPathSegments().get(3)); + else if (components.getPathSegments().size() == 1 && !components.getQueryParams().isEmpty()) readByCondition(connection, resourceTypeName, - parameterConverter.urlDecodeQueryParameters(componentes.getQueryParams())); + parameterConverter.urlDecodeQueryParameters(components.getQueryParams())); else { Response response = responseGenerator.badReadRequestUrl(index, requestUrl); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/UpdateCommand.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/UpdateCommand.java index 104367211..487d61db6 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/UpdateCommand.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/UpdateCommand.java @@ -107,10 +107,10 @@ public UpdateCommand(int index, Identity identity, PreferReturnType returnType, public void preExecute(Map idTranslationTable, Connection connection, ValidationHelper validationHelper, SnapshotGenerator snapshotGenerator) { - UriComponents eruComponentes = UriComponentsBuilder.fromUriString(entry.getRequest().getUrl()).build(); + UriComponents components = UriComponentsBuilder.fromUriString(entry.getRequest().getUrl()).build(); // check standard update request url: e.g. Patient/123 - if (eruComponentes.getPathSegments().size() == 2 && eruComponentes.getQueryParams().isEmpty()) + if (components.getPathSegments().size() == 2 && components.getQueryParams().isEmpty()) { if (!entry.hasFullUrl() || entry.getFullUrl().startsWith(URL_UUID_PREFIX)) { @@ -139,8 +139,8 @@ else if (resource.getIdElement().getIdPart().startsWith(URL_UUID_PREFIX)) Response response = responseGenerator.badBundleEntryFullUrl(index, entry.getFullUrl()); throw new WebApplicationException(response); } - else if (!expectedResourceTypeName.equals(eruComponentes.getPathSegments().get(0)) - || !expectedId.equals(eruComponentes.getPathSegments().get(1))) + else if (!expectedResourceTypeName.equals(components.getPathSegments().get(0)) + || !expectedId.equals(components.getPathSegments().get(1))) { Response response = responseGenerator.badUpdateRequestUrl(index, entry.getRequest().getUrl()); throw new WebApplicationException(response); @@ -148,7 +148,7 @@ else if (!expectedResourceTypeName.equals(eruComponentes.getPathSegments().get(0 } // check conditional update request url: e.g. Patient?... - else if (eruComponentes.getPathSegments().size() == 1 && !eruComponentes.getQueryParams().isEmpty()) + else if (components.getPathSegments().size() == 1 && !components.getQueryParams().isEmpty()) { if (!entry.getFullUrl().startsWith(URL_UUID_PREFIX)) { @@ -183,10 +183,10 @@ else if (resource.hasIdElement() && !entry.getFullUrl().equals(resource.getIdEle private boolean addMissingIdToTranslationTableAndCheckConditionFindsResource(Map idTranslationTable, Connection connection) { - UriComponents componentes = UriComponentsBuilder.fromUriString(entry.getRequest().getUrl()).build(); - String resourceTypeName = componentes.getPathSegments().get(0); + UriComponents components = UriComponentsBuilder.fromUriString(entry.getRequest().getUrl()).build(); + String resourceTypeName = components.getPathSegments().get(0); Map> queryParameters = parameterConverter - .urlDecodeQueryParameters(componentes.getQueryParams()); + .urlDecodeQueryParameters(components.getQueryParams()); if (Arrays.stream(SearchQuery.STANDARD_PARAMETERS).anyMatch(queryParameters::containsKey)) { diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/subscription/MatcherFactory.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/subscription/MatcherFactory.java index 77edecfac..8f7812ae7 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/subscription/MatcherFactory.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/subscription/MatcherFactory.java @@ -41,10 +41,10 @@ public MatcherFactory(Map> daosByResourc public Optional createMatcher(String uri) { - UriComponents componentes = UriComponentsBuilder.fromUriString(uri).build(); - String path = componentes.getPath(); + UriComponents components = UriComponentsBuilder.fromUriString(uri).build(); + String path = components.getPath(); - MultiValueMap queryParameters = componentes.getQueryParams(); + MultiValueMap queryParameters = components.getQueryParams(); if (daosByResourceName.containsKey(path)) { diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java index 6fd7d0b5f..eddaa9c70 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java @@ -336,8 +336,8 @@ private void checkAlreadyExists(HttpHeaders headers) throws WebApplicationExcept if (!ifNoneExistHeaderValue.contains("?")) ifNoneExistHeaderValue = '?' + ifNoneExistHeaderValue; - UriComponents componentes = UriComponentsBuilder.fromUriString(ifNoneExistHeaderValue).build(); - String path = componentes.getPath(); + UriComponents components = UriComponentsBuilder.fromUriString(ifNoneExistHeaderValue).build(); + String path = components.getPath(); if (path != null && !path.isBlank()) { Response response = responseGenerator.badIfNoneExistHeaderValue("no resource", ifNoneExistHeader.get()); @@ -345,7 +345,7 @@ private void checkAlreadyExists(HttpHeaders headers) throws WebApplicationExcept } Map> queryParameters = parameterConverter - .urlDecodeQueryParameters(componentes.getQueryParams()); + .urlDecodeQueryParameters(components.getQueryParams()); if (Arrays.stream(SearchQuery.STANDARD_PARAMETERS).anyMatch(queryParameters::containsKey)) { logger.warn( From 9ba378e3ba2c53fa44c1804d78f9bd015e248e4c Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 19:37:42 +0200 Subject: [PATCH 11/56] typo fix artefact -> artifact (different upper/lower cases) ARTEFACT -> ARTIFACT artefact -> artifact Artefact -> Artifact --- .../dsf/fhir/service/ResourceReference.java | 38 +++++++++---------- .../dao/command/ReferencesHelperImpl.java | 12 +++--- .../fhir/service/ReferenceResolverImpl.java | 18 ++++----- .../impl/AbstractResourceServiceImpl.java | 6 +-- .../secure/AbstractResourceServiceSecure.java | 2 +- .../dao/command/ResourceReferenceTest.java | 20 +++++----- .../ResearchStudyIntegrationTest.java | 24 ++++++------ 7 files changed, 60 insertions(+), 60 deletions(-) diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/service/ResourceReference.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/service/ResourceReference.java index 40adfee83..dba0852bd 100644 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/service/ResourceReference.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/service/ResourceReference.java @@ -122,23 +122,23 @@ public enum ReferenceType /** * temporary url in RelatedArtifact starting with urn:uuid: */ - RELATED_ARTEFACT_TEMPORARY_URL, + RELATED_ARTIFACT_TEMPORARY_URL, /** * conditional url in RelatedArtifact */ - RELATED_ARTEFACT_CONDITIONAL_URL, + RELATED_ARTIFACT_CONDITIONAL_URL, /** * literal url in RelatedArtifact to a resource on this server */ - RELATED_ARTEFACT_LITERAL_INTERNAL_URL, + RELATED_ARTIFACT_LITERAL_INTERNAL_URL, /** * literal url in RelatedArtifact to a resource on an external server */ - RELATED_ARTEFACT_LITERAL_EXTERNAL_URL, + RELATED_ARTIFACT_LITERAL_EXTERNAL_URL, /** * unknown url in RelatedArtifact */ - RELATED_ARTEFACT_UNKNOWN_URL, + RELATED_ARTIFACT_UNKNOWN_URL, /** * temporary url in Attachment starting with urn:uuid: */ @@ -262,7 +262,7 @@ else if (hasAttachment()) else if (hasCanonical()) return canonical.getValue(); else - throw new IllegalArgumentException("reference, related artefact, attachment or canonical not set"); + throw new IllegalArgumentException("reference, related artifact, attachment or canonical not set"); } public List> getReferenceTypes() @@ -280,11 +280,11 @@ public boolean supportsType(Class type) * * @param localServerBase * not null - * @return one of this priority list: {@link ReferenceType#RELATED_ARTEFACT_TEMPORARY_URL}, - * {@link ReferenceType#RELATED_ARTEFACT_LITERAL_INTERNAL_URL}, - * {@link ReferenceType#RELATED_ARTEFACT_LITERAL_EXTERNAL_URL}, - * {@link ReferenceType#RELATED_ARTEFACT_CONDITIONAL_URL}, - * {@link ReferenceType#RELATED_ARTEFACT_UNKNOWN_URL}, {@link ReferenceType#ATTACHMENT_TEMPORARY_URL}, + * @return one of this priority list: {@link ReferenceType#RELATED_ARTIFACT_TEMPORARY_URL}, + * {@link ReferenceType#RELATED_ARTIFACT_LITERAL_INTERNAL_URL}, + * {@link ReferenceType#RELATED_ARTIFACT_LITERAL_EXTERNAL_URL}, + * {@link ReferenceType#RELATED_ARTIFACT_CONDITIONAL_URL}, + * {@link ReferenceType#RELATED_ARTIFACT_UNKNOWN_URL}, {@link ReferenceType#ATTACHMENT_TEMPORARY_URL}, * {@link ReferenceType#ATTACHMENT_LITERAL_INTERNAL_URL}, * {@link ReferenceType#ATTACHMENT_LITERAL_EXTERNAL_URL}, {@link ReferenceType#ATTACHMENT_CONDITIONAL_URL}, * {@link ReferenceType#ATTACHMENT_UNKNOWN_URL}, {@link ReferenceType#TEMPORARY}, @@ -301,24 +301,24 @@ public ReferenceType getType(String localServerBase) { Matcher tempIdRefMatcher = TEMP_ID_PATTERN.matcher(relatedArtifact.getUrl()); if (tempIdRefMatcher.matches()) - return ReferenceType.RELATED_ARTEFACT_TEMPORARY_URL; + return ReferenceType.RELATED_ARTIFACT_TEMPORARY_URL; Matcher idRefMatcher = ID_PATTERN.matcher(relatedArtifact.getUrl()); if (idRefMatcher.matches()) { IdType id = new IdType(relatedArtifact.getUrl()); if (!id.isAbsolute() || localServerBase.equals(id.getBaseUrl())) - return ReferenceType.RELATED_ARTEFACT_LITERAL_INTERNAL_URL; + return ReferenceType.RELATED_ARTIFACT_LITERAL_INTERNAL_URL; else - return ReferenceType.RELATED_ARTEFACT_LITERAL_EXTERNAL_URL; + return ReferenceType.RELATED_ARTIFACT_LITERAL_EXTERNAL_URL; } Matcher conditionalRefMatcher = CONDITIONAL_REF_PATTERN.matcher(relatedArtifact.getUrl()); if (conditionalRefMatcher.matches()) - return ReferenceType.RELATED_ARTEFACT_CONDITIONAL_URL; + return ReferenceType.RELATED_ARTIFACT_CONDITIONAL_URL; } - return ReferenceType.RELATED_ARTEFACT_UNKNOWN_URL; + return ReferenceType.RELATED_ARTIFACT_UNKNOWN_URL; } else if (attachment != null) { @@ -383,7 +383,7 @@ else if (canonical != null) return ReferenceType.UNKNOWN; } else - throw new IllegalStateException("Either reference, related artefact, attachment or canonical expected"); + throw new IllegalStateException("Either reference, related artifact, attachment or canonical expected"); } public String getLocation() @@ -395,14 +395,14 @@ public String getLocation() * @param localServerBase * not null * @return empty String if the type of this {@link ResourceReference} is not {@link ReferenceType#LITERAL_EXTERNAL}, - * {@link ReferenceType#RELATED_ARTEFACT_LITERAL_EXTERNAL_URL} or + * {@link ReferenceType#RELATED_ARTIFACT_LITERAL_EXTERNAL_URL} or * {@link ReferenceType#ATTACHMENT_LITERAL_EXTERNAL_URL} */ public String getServerBase(String localServerBase) { Objects.requireNonNull(localServerBase, "localServerBase"); - if (EnumSet.of(ReferenceType.LITERAL_EXTERNAL, ReferenceType.RELATED_ARTEFACT_LITERAL_EXTERNAL_URL, + if (EnumSet.of(ReferenceType.LITERAL_EXTERNAL, ReferenceType.RELATED_ARTIFACT_LITERAL_EXTERNAL_URL, ReferenceType.ATTACHMENT_LITERAL_EXTERNAL_URL).contains(getType(localServerBase))) return new IdType(getValue()).getBaseUrl(); else diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReferencesHelperImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReferencesHelperImpl.java index 4d73a0cd9..ac9d0098c 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReferencesHelperImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/ReferencesHelperImpl.java @@ -85,7 +85,7 @@ private Optional resolveTemporaryOrConditionalReferenceOrLiter case TEMPORARY -> resolveTemporary(reference, idTranslationTable, reference.getReference()::getReference, reference.getReference()::setReferenceElement); - case RELATED_ARTEFACT_TEMPORARY_URL -> resolveTemporary(reference, idTranslationTable, + case RELATED_ARTIFACT_TEMPORARY_URL -> resolveTemporary(reference, idTranslationTable, reference.getRelatedArtifact()::getUrl, newIdToAbsoluteUrl(reference.getRelatedArtifact()::setUrl)); case ATTACHMENT_TEMPORARY_URL -> resolveTemporary(reference, idTranslationTable, @@ -95,13 +95,13 @@ private Optional resolveTemporaryOrConditionalReferenceOrLiter resolveConditional(reference, connection, target -> reference.getReference().setReferenceElement( new IdType(target.getResourceType().name(), target.getIdElement().getIdPart()))); - case RELATED_ARTEFACT_CONDITIONAL_URL -> + case RELATED_ARTIFACT_CONDITIONAL_URL -> resolveConditional(reference, connection, targetToAbsoluteUrl(reference.getRelatedArtifact()::setUrl)); case ATTACHMENT_CONDITIONAL_URL -> resolveConditional(reference, connection, targetToAbsoluteUrl(reference.getAttachment()::setUrl)); - case RELATED_ARTEFACT_LITERAL_INTERNAL_URL -> resolveLiteralInternalUrl(reference::getRelatedArtifact, + case RELATED_ARTIFACT_LITERAL_INTERNAL_URL -> resolveLiteralInternalUrl(reference::getRelatedArtifact, RelatedArtifact::getUrl, RelatedArtifact::setUrl); case ATTACHMENT_LITERAL_INTERNAL_URL -> @@ -227,10 +227,10 @@ private Optional checkReference(ResourceReference reference, C { return switch (reference.getType(serverBase)) { - case LITERAL_INTERNAL, RELATED_ARTEFACT_LITERAL_INTERNAL_URL, ATTACHMENT_LITERAL_INTERNAL_URL -> + case LITERAL_INTERNAL, RELATED_ARTIFACT_LITERAL_INTERNAL_URL, ATTACHMENT_LITERAL_INTERNAL_URL -> referenceResolver.checkLiteralInternalReference(resource, reference, connection, index); - case LITERAL_EXTERNAL, RELATED_ARTEFACT_LITERAL_EXTERNAL_URL, ATTACHMENT_LITERAL_EXTERNAL_URL -> + case LITERAL_EXTERNAL, RELATED_ARTIFACT_LITERAL_EXTERNAL_URL, ATTACHMENT_LITERAL_EXTERNAL_URL -> referenceResolver.checkLiteralExternalReference(resource, reference, index); case LOGICAL -> referenceResolver.checkLogicalReference(resource, reference, connection, index); @@ -238,7 +238,7 @@ private Optional checkReference(ResourceReference reference, C case CANONICAL -> referenceResolver.checkCanonicalReference(resource, reference, connection, index); // unknown URLs to non FHIR servers in related artifacts must not be checked - case RELATED_ARTEFACT_UNKNOWN_URL, ATTACHMENT_UNKNOWN_URL -> Optional.empty(); + case RELATED_ARTIFACT_UNKNOWN_URL, ATTACHMENT_UNKNOWN_URL -> Optional.empty(); case UNKNOWN -> Optional.of(responseGenerator.unknownReference(resource, reference, index)); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/service/ReferenceResolverImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/service/ReferenceResolverImpl.java index 795f34adc..e169bdd9b 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/service/ReferenceResolverImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/service/ReferenceResolverImpl.java @@ -94,7 +94,7 @@ public boolean referenceCanBeResolved(ResourceReference reference, Connection co return switch (reference.getType(serverBase)) { - case LITERAL_EXTERNAL, RELATED_ARTEFACT_LITERAL_EXTERNAL_URL, ATTACHMENT_LITERAL_EXTERNAL_URL -> + case LITERAL_EXTERNAL, RELATED_ARTIFACT_LITERAL_EXTERNAL_URL, ATTACHMENT_LITERAL_EXTERNAL_URL -> clientProvider.endpointExists(reference.getServerBase(serverBase)); case LOGICAL -> exceptionHandler.handleSqlException( @@ -118,9 +118,9 @@ public Optional resolveReference(ResourceReference reference, Connecti return switch (type) { case LITERAL_INTERNAL -> resolveLiteralInternalReference(reference, connection); - case LITERAL_EXTERNAL, RELATED_ARTEFACT_LITERAL_EXTERNAL_URL, ATTACHMENT_LITERAL_EXTERNAL_URL -> + case LITERAL_EXTERNAL, RELATED_ARTIFACT_LITERAL_EXTERNAL_URL, ATTACHMENT_LITERAL_EXTERNAL_URL -> resolveLiteralExternalReference(reference); - case CONDITIONAL, RELATED_ARTEFACT_CONDITIONAL_URL, ATTACHMENT_CONDITIONAL_URL -> + case CONDITIONAL, RELATED_ARTIFACT_CONDITIONAL_URL, ATTACHMENT_CONDITIONAL_URL -> resolveConditionalReference(reference, connection); case LOGICAL -> resolveLogicalReference(reference, connection); @@ -189,7 +189,7 @@ private Optional resolveLiteralExternalReference(ResourceReference ref { Objects.requireNonNull(reference, "reference"); throwIfReferenceTypeUnexpected(reference.getType(serverBase), EnumSet.of(ReferenceType.LITERAL_EXTERNAL, - ReferenceType.RELATED_ARTEFACT_LITERAL_EXTERNAL_URL, ReferenceType.ATTACHMENT_LITERAL_EXTERNAL_URL)); + ReferenceType.RELATED_ARTIFACT_LITERAL_EXTERNAL_URL, ReferenceType.ATTACHMENT_LITERAL_EXTERNAL_URL)); String remoteServerBase = reference.getServerBase(serverBase); Optional client = clientProvider.getClient(remoteServerBase); @@ -234,7 +234,7 @@ private Optional resolveConditionalReference(ResourceReference referen ReferenceType referenceType = reference.getType(serverBase); throwIfReferenceTypeUnexpected(referenceType, EnumSet.of(ReferenceType.CONDITIONAL, - ReferenceType.RELATED_ARTEFACT_CONDITIONAL_URL, ReferenceType.ATTACHMENT_CONDITIONAL_URL)); + ReferenceType.RELATED_ARTIFACT_CONDITIONAL_URL, ReferenceType.ATTACHMENT_CONDITIONAL_URL)); String referenceValue = reference.getValue(); String referenceLocation = reference.getLocation(); @@ -325,7 +325,7 @@ private Optional search(Connection connection, ResourceDao referenc String unsupportedQueryParametersString = unsupportedQueryParameters.stream() .map(SearchQueryParameterError::toString).collect(Collectors.joining("; ")); - if (EnumSet.of(ReferenceType.CONDITIONAL, ReferenceType.RELATED_ARTEFACT_CONDITIONAL_URL, + if (EnumSet.of(ReferenceType.CONDITIONAL, ReferenceType.RELATED_ARTIFACT_CONDITIONAL_URL, ReferenceType.ATTACHMENT_CONDITIONAL_URL).contains(referenceType)) { logger.warn("Conditional reference {} at {} in resource contains unsupported queryparameter{} {}", @@ -357,7 +357,7 @@ private Optional search(Connection connection, ResourceDao referenc logger.warn("Found {} matches for reference at {} with identifier '{}|{}'", overallCount, resourceReference.getLocation(), resourceReference.getReference().getIdentifier().getSystem(), resourceReference.getReference().getIdentifier().getValue()); - else if (EnumSet.of(ReferenceType.CONDITIONAL, ReferenceType.RELATED_ARTEFACT_CONDITIONAL_URL, + else if (EnumSet.of(ReferenceType.CONDITIONAL, ReferenceType.RELATED_ARTIFACT_CONDITIONAL_URL, ReferenceType.ATTACHMENT_CONDITIONAL_URL).contains(referenceType)) logger.warn("Found {} matches for reference at {} with condition '{}'", overallCount, resourceReference.getLocation(), @@ -386,7 +386,7 @@ public Optional checkLiteralInternalReference(Resource resourc Objects.requireNonNull(reference, "reference"); Objects.requireNonNull(connection, "connection"); throwIfReferenceTypeUnexpected(reference.getType(serverBase), EnumSet.of(ReferenceType.LITERAL_INTERNAL, - ReferenceType.RELATED_ARTEFACT_LITERAL_INTERNAL_URL, ReferenceType.ATTACHMENT_LITERAL_INTERNAL_URL)); + ReferenceType.RELATED_ARTIFACT_LITERAL_INTERNAL_URL, ReferenceType.ATTACHMENT_LITERAL_INTERNAL_URL)); IdType id = new IdType(reference.getValue()); Optional> referenceDao = daoProvider.getDao(id.getResourceType()); @@ -424,7 +424,7 @@ public Optional checkLiteralExternalReference(Resource resourc Objects.requireNonNull(resource, "resource"); Objects.requireNonNull(reference, "reference"); throwIfReferenceTypeUnexpected(reference.getType(serverBase), EnumSet.of(ReferenceType.LITERAL_EXTERNAL, - ReferenceType.RELATED_ARTEFACT_LITERAL_EXTERNAL_URL, ReferenceType.ATTACHMENT_LITERAL_EXTERNAL_URL)); + ReferenceType.RELATED_ARTIFACT_LITERAL_EXTERNAL_URL, ReferenceType.ATTACHMENT_LITERAL_EXTERNAL_URL)); String remoteServerBase = reference.getServerBase(serverBase); String referenceValue = reference.getValue(); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java index eddaa9c70..dcf65d17c 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/AbstractResourceServiceImpl.java @@ -300,10 +300,10 @@ private Optional checkReference(Resource resource, Connection { return switch (reference.getType(serverBase)) { - case LITERAL_INTERNAL, RELATED_ARTEFACT_LITERAL_INTERNAL_URL, ATTACHMENT_LITERAL_INTERNAL_URL -> + case LITERAL_INTERNAL, RELATED_ARTIFACT_LITERAL_INTERNAL_URL, ATTACHMENT_LITERAL_INTERNAL_URL -> referenceResolver.checkLiteralInternalReference(resource, reference, connection); - case LITERAL_EXTERNAL, RELATED_ARTEFACT_LITERAL_EXTERNAL_URL, ATTACHMENT_LITERAL_EXTERNAL_URL -> + case LITERAL_EXTERNAL, RELATED_ARTIFACT_LITERAL_EXTERNAL_URL, ATTACHMENT_LITERAL_EXTERNAL_URL -> referenceResolver.checkLiteralExternalReference(resource, reference); case LOGICAL -> referenceResolver.checkLogicalReference(resource, reference, connection); @@ -311,7 +311,7 @@ private Optional checkReference(Resource resource, Connection case CANONICAL -> referenceResolver.checkCanonicalReference(resource, reference, connection); // unknown URLs to non FHIR servers in related artifacts must not be checked - case RELATED_ARTEFACT_UNKNOWN_URL, ATTACHMENT_UNKNOWN_URL -> Optional.empty(); + case RELATED_ARTIFACT_UNKNOWN_URL, ATTACHMENT_UNKNOWN_URL -> Optional.empty(); case UNKNOWN -> Optional.of(responseGenerator.unknownReference(resource, reference)); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java index e3742372b..38834f681 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java @@ -230,7 +230,7 @@ private void resolveLiteralInternalRelatedArtifactOrAttachmentUrls(R resource) return; referenceExtractor.getReferences(resource) - .filter(ref -> ReferenceType.RELATED_ARTEFACT_LITERAL_INTERNAL_URL.equals(ref.getType(serverBase)) + .filter(ref -> ReferenceType.RELATED_ARTIFACT_LITERAL_INTERNAL_URL.equals(ref.getType(serverBase)) || ReferenceType.ATTACHMENT_LITERAL_INTERNAL_URL.equals(ref.getType(serverBase))) .forEach(this::resolveLiteralInternalRelatedArtifactOrAttachmentUrl); } diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/command/ResourceReferenceTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/command/ResourceReferenceTest.java index 9649f5f7d..49e99f4ef 100755 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/command/ResourceReferenceTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/dao/command/ResourceReferenceTest.java @@ -127,7 +127,7 @@ public void testGetTypeLogical() throws Exception } @Test - public void testGetTypeRelatedArtefact() throws Exception + public void testGetTypeRelatedArtifact() throws Exception { var r1 = new ResourceReference("Foo.bar", new RelatedArtifact().setType(DOCUMENTATION).setUrl("urn:uuid:" + UUID.randomUUID().toString())); @@ -150,19 +150,19 @@ public void testGetTypeRelatedArtefact() throws Exception var r9 = new ResourceReference("Foo.bar", new RelatedArtifact().setType(DOCUMENTATION).setUrl(UUID.randomUUID().toString())); - assertEquals(ReferenceType.RELATED_ARTEFACT_TEMPORARY_URL, r1.getType(serverBase)); + assertEquals(ReferenceType.RELATED_ARTIFACT_TEMPORARY_URL, r1.getType(serverBase)); - assertEquals(ReferenceType.RELATED_ARTEFACT_CONDITIONAL_URL, r2.getType(serverBase)); + assertEquals(ReferenceType.RELATED_ARTIFACT_CONDITIONAL_URL, r2.getType(serverBase)); - assertEquals(ReferenceType.RELATED_ARTEFACT_LITERAL_INTERNAL_URL, r3.getType(serverBase)); - assertEquals(ReferenceType.RELATED_ARTEFACT_LITERAL_INTERNAL_URL, r4.getType(serverBase)); + assertEquals(ReferenceType.RELATED_ARTIFACT_LITERAL_INTERNAL_URL, r3.getType(serverBase)); + assertEquals(ReferenceType.RELATED_ARTIFACT_LITERAL_INTERNAL_URL, r4.getType(serverBase)); - assertEquals(ReferenceType.RELATED_ARTEFACT_LITERAL_EXTERNAL_URL, r5.getType(serverBase)); - assertEquals(ReferenceType.RELATED_ARTEFACT_LITERAL_EXTERNAL_URL, r6.getType(serverBase)); + assertEquals(ReferenceType.RELATED_ARTIFACT_LITERAL_EXTERNAL_URL, r5.getType(serverBase)); + assertEquals(ReferenceType.RELATED_ARTIFACT_LITERAL_EXTERNAL_URL, r6.getType(serverBase)); - assertEquals(ReferenceType.RELATED_ARTEFACT_UNKNOWN_URL, r7.getType(serverBase)); - assertEquals(ReferenceType.RELATED_ARTEFACT_UNKNOWN_URL, r8.getType(serverBase)); - assertEquals(ReferenceType.RELATED_ARTEFACT_UNKNOWN_URL, r9.getType(serverBase)); + assertEquals(ReferenceType.RELATED_ARTIFACT_UNKNOWN_URL, r7.getType(serverBase)); + assertEquals(ReferenceType.RELATED_ARTIFACT_UNKNOWN_URL, r8.getType(serverBase)); + assertEquals(ReferenceType.RELATED_ARTIFACT_UNKNOWN_URL, r9.getType(serverBase)); } @Test diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/ResearchStudyIntegrationTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/ResearchStudyIntegrationTest.java index 2ca3ec010..21b8f4a38 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/ResearchStudyIntegrationTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/ResearchStudyIntegrationTest.java @@ -69,7 +69,7 @@ public void testSearchByGroupId() throws Exception } @Test - public void testCreateWithRelatedArtefactUnknownUrl() throws Exception + public void testCreateWithRelatedArtifactUnknownUrl() throws Exception { String url = "https://foo.bar"; ResearchStudy researchStudy = getResearchStudy(url); @@ -85,7 +85,7 @@ public void testCreateWithRelatedArtefactUnknownUrl() throws Exception } @Test - public void testCreateViaBundleWithRelatedArtefactUnknownUrl() throws Exception + public void testCreateViaBundleWithRelatedArtifactUnknownUrl() throws Exception { String url = "https://foo.bar"; ResearchStudy researchStudy = getResearchStudy(url); @@ -113,7 +113,7 @@ public void testCreateViaBundleWithRelatedArtefactUnknownUrl() throws Exception } @Test - public void testCreateViaBundleWithRelatedArtefactTemporaryUrl() throws Exception + public void testCreateViaBundleWithRelatedArtifactTemporaryUrl() throws Exception { String binaryUrl = "urn:uuid:" + UUID.randomUUID().toString(); Binary binary = getBinary(); @@ -152,7 +152,7 @@ public void testCreateViaBundleWithRelatedArtefactTemporaryUrl() throws Exceptio } @Test - public void testCreateViaBundleWithRelatedArtefactConditionalUrl() throws Exception + public void testCreateViaBundleWithRelatedArtifactConditionalUrl() throws Exception { Binary binary = getBinary(); Binary binaryResult = getWebserviceClient().create(binary); @@ -187,7 +187,7 @@ public void testCreateViaBundleWithRelatedArtefactConditionalUrl() throws Except } @Test - public void testCreateViaBundleWithRelatedArtefactRelativeLiteralInternalUrl() throws Exception + public void testCreateViaBundleWithRelatedArtifactRelativeLiteralInternalUrl() throws Exception { Binary binary = getBinary(); Binary binaryResult = getWebserviceClient().create(binary); @@ -222,7 +222,7 @@ public void testCreateViaBundleWithRelatedArtefactRelativeLiteralInternalUrl() t } @Test - public void testCreateViaBundleWithRelatedArtefactAbsoluteLiteralInternalUrl() throws Exception + public void testCreateViaBundleWithRelatedArtifactAbsoluteLiteralInternalUrl() throws Exception { Binary binary = getBinary(); Binary binaryResult = getWebserviceClient().create(binary); @@ -258,7 +258,7 @@ public void testCreateViaBundleWithRelatedArtefactAbsoluteLiteralInternalUrl() t } @Test - public void testCreateWithRelatedArtefactRelativeLiteralInternalUrl() throws Exception + public void testCreateWithRelatedArtifactRelativeLiteralInternalUrl() throws Exception { Binary binary = getBinary(); Binary binaryResult = getWebserviceClient().create(binary); @@ -282,7 +282,7 @@ public void testCreateWithRelatedArtefactRelativeLiteralInternalUrl() throws Exc } @Test - public void testCreateWithRelatedArtefactRelativeLiteralInternalUrlNonExisting() throws Exception + public void testCreateWithRelatedArtifactRelativeLiteralInternalUrlNonExisting() throws Exception { ResearchStudy researchStudy = getResearchStudy("Binary/" + UUID.randomUUID().toString()); @@ -290,7 +290,7 @@ public void testCreateWithRelatedArtefactRelativeLiteralInternalUrlNonExisting() } @Test - public void testCreateViaBundleWithRelatedArtefactRelativeLiteralInternalUrlNonExisting() throws Exception + public void testCreateViaBundleWithRelatedArtifactRelativeLiteralInternalUrlNonExisting() throws Exception { ResearchStudy researchStudy = getResearchStudy("Binary/" + UUID.randomUUID().toString()); @@ -302,7 +302,7 @@ public void testCreateViaBundleWithRelatedArtefactRelativeLiteralInternalUrlNonE } @Test - public void testCreateWithRelatedArtefactAbsoluteLiteralInternalUrl() throws Exception + public void testCreateWithRelatedArtifactAbsoluteLiteralInternalUrl() throws Exception { Binary binary = getBinary(); Binary binaryResult = getWebserviceClient().create(binary); @@ -326,7 +326,7 @@ public void testCreateWithRelatedArtefactAbsoluteLiteralInternalUrl() throws Exc } @Test - public void testCreateWithRelatedArtefactAbsoluteLiteralExternalUrl() throws Exception + public void testCreateWithRelatedArtifactAbsoluteLiteralExternalUrl() throws Exception { String literalExternalUrl = "https://www.foo.bar/fhir/Binary/" + UUID.randomUUID().toString(); ResearchStudy researchStudy = getResearchStudy(literalExternalUrl); @@ -342,7 +342,7 @@ public void testCreateWithRelatedArtefactAbsoluteLiteralExternalUrl() throws Exc } @Test - public void testCreateViaBundleWithRelatedArtefactAbsoluteLiteralExternalUrl() throws Exception + public void testCreateViaBundleWithRelatedArtifactAbsoluteLiteralExternalUrl() throws Exception { String literalExternalUrl = "https://www.foo.bar/fhir/Binary/" + UUID.randomUUID().toString(); ResearchStudy researchStudy = getResearchStudy(literalExternalUrl); From 83d8c9e74028b8b6f6a17c82737a178fce73d0af Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 20:01:19 +0200 Subject: [PATCH 12/56] fixed incorrect role in log message --- .../authorization/QuestionnaireResponseAuthorizationRule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/QuestionnaireResponseAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/QuestionnaireResponseAuthorizationRule.java index 039986a81..9fe8e520f 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/QuestionnaireResponseAuthorizationRule.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/QuestionnaireResponseAuthorizationRule.java @@ -108,7 +108,7 @@ public Optional reasonCreateAllowed(Connection connection, Identity iden else { logger.warn("Create of QuestionnaireResponse unauthorized for identity '{}', no role {}", - identity.getName(), deleteRole); + identity.getName(), createRole); return Optional.empty(); } From eeac609417aaeb68a213695e006570f1d4b87209 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 20:03:26 +0200 Subject: [PATCH 13/56] typo fix in log messages: of -> or, invalud -> invalid --- .../src/main/java/dev/dsf/common/auth/conf/RoleConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/RoleConfig.java b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/RoleConfig.java index 744d65d60..fd707c43d 100644 --- a/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/RoleConfig.java +++ b/dsf-common/dsf-common-auth/src/main/java/dev/dsf/common/auth/conf/RoleConfig.java @@ -278,7 +278,7 @@ else if (!EMAIL_PATTERN.matcher(value.trim()).matches()) if (value == null || value.isBlank()) { logger.warn( - "Ignoring empty of blank practitioner-role in rule '{}'", + "Ignoring empty or blank practitioner-role in rule '{}'", mappingKey); return null; } @@ -312,7 +312,7 @@ else if (mappingKey != null && mappingKey instanceof String }); } else - logger.warn("Ignoring invalud rule '{}'", mapping); + logger.warn("Ignoring invalid rule '{}'", mapping); }); } } From 59b73efbdb5860c483793ead98a5fe517f8f2016 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 20:06:10 +0200 Subject: [PATCH 14/56] typo fix in log message: crv -> alg --- .../dsf-common-oidc/src/main/java/dev/dsf/common/oidc/Jwks.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-common/dsf-common-oidc/src/main/java/dev/dsf/common/oidc/Jwks.java b/dsf-common/dsf-common-oidc/src/main/java/dev/dsf/common/oidc/Jwks.java index 34a1200b4..25f40bf6b 100644 --- a/dsf-common/dsf-common-oidc/src/main/java/dev/dsf/common/oidc/Jwks.java +++ b/dsf-common/dsf-common-oidc/src/main/java/dev/dsf/common/oidc/Jwks.java @@ -136,7 +136,7 @@ private Algorithm toEcdsaAlgorithm() case "ES512" -> Algorithm.ECDSA512(keyProvider); default -> { - logger.warn("JWKS crv property value '{}' not one of 'ES256', 'ES384' or 'ES512'", alg); + logger.warn("JWKS alg property value '{}' not one of 'ES256', 'ES384' or 'ES512'", alg); yield null; } }; From f66bd6be291f0631392c437d9d5da5dc4f41d03f Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 20:15:02 +0200 Subject: [PATCH 15/56] typo fix buisness-key -> business-key --- .../dev/dsf/bpe/v2/activity/task/BusinessKeyStrategies.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/task/BusinessKeyStrategies.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/task/BusinessKeyStrategies.java index b74ab68d2..68f80e6ae 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/task/BusinessKeyStrategies.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/task/BusinessKeyStrategies.java @@ -36,7 +36,7 @@ public String get(Variables variables, Target target) } }, /** - * Generates an alternative buisness-key for the current process instance and uses the alternative when sending Task + * Generates an alternative business-key for the current process instance and uses the alternative when sending Task * resources. *

* This can be used to hide the current business-key from the target, but allows the target to reply using the send From b4e73cfadf71bbcafe03002928435d386374950e Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 20:15:42 +0200 Subject: [PATCH 16/56] removed empty row --- .../java/dev/dsf/bpe/v2/activity/task/BusinessKeyStrategies.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/task/BusinessKeyStrategies.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/task/BusinessKeyStrategies.java index 68f80e6ae..01c81da98 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/task/BusinessKeyStrategies.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/task/BusinessKeyStrategies.java @@ -66,7 +66,6 @@ public String get(Variables variables, Target target) { return createBusinessKey(); } - }; private static String createBusinessKey() From f0e21a62e1816a0aebe8cca8396fe37c6673e75f Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 20:25:46 +0200 Subject: [PATCH 17/56] typo fix in constructor parameter name organizatioRoleSystem -> organizationRoleSystem --- .../main/java/dev/dsf/bpe/v2/service/process/Role.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/process/Role.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/process/Role.java index 111432cce..bc70746ee 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/process/Role.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/process/Role.java @@ -42,22 +42,22 @@ public class Role implements Recipient, Requester private final String practitionerRoleSystem; private final String practitionerRoleCode; - public Role(boolean localIdentity, String parentOrganizationIdentifier, String organizatioRoleSystem, + public Role(boolean localIdentity, String parentOrganizationIdentifier, String organizationRoleSystem, String organizationRoleCode, String practitionerRoleSystem, String practitionerRoleCode) { Objects.requireNonNull(parentOrganizationIdentifier, "parentOrganizationIdentifier"); if (parentOrganizationIdentifier.isBlank()) throw new IllegalArgumentException("parentOrganizationIdentifier blank"); - Objects.requireNonNull(organizatioRoleSystem, "organizatioRoleSystem"); - if (organizatioRoleSystem.isBlank()) - throw new IllegalArgumentException("organizatioRoleSystem blank"); + Objects.requireNonNull(organizationRoleSystem, "organizationRoleSystem"); + if (organizationRoleSystem.isBlank()) + throw new IllegalArgumentException("organizationRoleSystem blank"); Objects.requireNonNull(organizationRoleCode, "organizationRoleCode"); if (organizationRoleCode.isBlank()) throw new IllegalArgumentException("organizationRoleCode blank"); this.localIdentity = localIdentity; this.parentOrganizationIdentifier = parentOrganizationIdentifier; - this.organizationRoleSystem = organizatioRoleSystem; + this.organizationRoleSystem = organizationRoleSystem; this.organizationRoleCode = organizationRoleCode; this.practitionerRoleSystem = practitionerRoleSystem; From 6aeda994a42f16be182488dad592bd2b9f4e24c1 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 20:39:39 +0200 Subject: [PATCH 18/56] fixed inverted logic in JavaDoc --- .../src/main/java/dev/dsf/bpe/v2/service/CryptoService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/CryptoService.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/CryptoService.java index 23d100b47..4de0be9ec 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/CryptoService.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/CryptoService.java @@ -326,7 +326,7 @@ default PrivateKey readPrivateKey(Path pem, char[] password) throws IOException /** * @param certificate * not null - * @return true if the given certificate not-after field is after {@link ZonedDateTime#now()} + * @return true if {@link ZonedDateTime#now()} is after the given certificates not-after field */ boolean isCertificateExpired(X509Certificate certificate); From 53fe6f966e4d92e3c8445bf91ab889e2cd85d082 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 21:10:40 +0200 Subject: [PATCH 19/56] typo fix Cient -> Client, Conditionaly -> Conditionally --- ...=> BasicWebserviceClientWithRetryImpl.java} | 5 +++-- .../bpe/client/dsf/WebserviceClientJersey.java | 8 ++++---- .../integration/BundleIntegrationTest.java | 6 +++--- .../OrganizationIntegrationTest.java | 4 ++-- .../fhir/integration/TaskIntegrationTest.java | 4 ++-- .../fhir/client/BasicFhirWebserviceClient.java | 2 +- ...asicFhirWebserviceClientWithRetryImpl.java} | 16 ++++++++-------- .../client/FhirWebserviceClientJersey.java | 18 +++++++++--------- .../dsf/fhir/client/PreferReturnMinimal.java | 4 ++-- .../client/PreferReturnMinimalRetryImpl.java | 8 ++++---- .../PreferReturnMinimalWithRetryImpl.java | 8 ++++---- .../dsf/fhir/client/PreferReturnOutcome.java | 4 ++-- .../client/PreferReturnOutcomeRetryImpl.java | 8 ++++---- .../PreferReturnOutcomeWithRetryImpl.java | 8 ++++---- .../dsf/fhir/client/PreferReturnResource.java | 4 ++-- 15 files changed, 54 insertions(+), 53 deletions(-) rename dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/dsf/{BasicWebserviceCientWithRetryImpl.java => BasicWebserviceClientWithRetryImpl.java} (85%) rename dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/{BasicFhirWebserviceCientWithRetryImpl.java => BasicFhirWebserviceClientWithRetryImpl.java} (88%) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/dsf/BasicWebserviceCientWithRetryImpl.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/dsf/BasicWebserviceClientWithRetryImpl.java similarity index 85% rename from dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/dsf/BasicWebserviceCientWithRetryImpl.java rename to dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/dsf/BasicWebserviceClientWithRetryImpl.java index ce01d55a2..1621112aa 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/dsf/BasicWebserviceCientWithRetryImpl.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/dsf/BasicWebserviceClientWithRetryImpl.java @@ -22,9 +22,10 @@ import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Resource; -class BasicWebserviceCientWithRetryImpl extends AbstractWebserviceClientJerseyWithRetry implements BasicWebserviceClient +class BasicWebserviceClientWithRetryImpl extends AbstractWebserviceClientJerseyWithRetry + implements BasicWebserviceClient { - BasicWebserviceCientWithRetryImpl(WebserviceClientJersey delegate, int nTimes, Duration delayMillis) + BasicWebserviceClientWithRetryImpl(WebserviceClientJersey delegate, int nTimes, Duration delayMillis) { super(delegate, nTimes, delayMillis); } diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/dsf/WebserviceClientJersey.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/dsf/WebserviceClientJersey.java index f9c531420..ef44e7918 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/dsf/WebserviceClientJersey.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/dsf/WebserviceClientJersey.java @@ -190,7 +190,7 @@ PreferReturn create(PreferReturnType returnType, Resource resource) throw handleError(response); } - PreferReturn createConditionaly(PreferReturnType returnType, Resource resource, String ifNoneExistCriteria) + PreferReturn createConditionally(PreferReturnType returnType, Resource resource, String ifNoneExistCriteria) { Objects.requireNonNull(returnType, "returnType"); Objects.requireNonNull(resource, "resource"); @@ -253,7 +253,7 @@ PreferReturn update(PreferReturnType returnType, Resource resource) throw handleError(response); } - PreferReturn updateConditionaly(PreferReturnType returnType, Resource resource, Map> criteria) + PreferReturn updateConditionally(PreferReturnType returnType, Resource resource, Map> criteria) { Objects.requireNonNull(returnType, "returnType"); Objects.requireNonNull(resource, "resource"); @@ -364,7 +364,7 @@ public BasicWebserviceClient withRetry(int nTimes, Duration delay) if (delay == null || delay.isNegative()) throw new IllegalArgumentException("delay null or negative"); - return new BasicWebserviceCientWithRetryImpl(this, nTimes, delay); + return new BasicWebserviceClientWithRetryImpl(this, nTimes, delay); } @Override @@ -373,6 +373,6 @@ public BasicWebserviceClient withRetryForever(Duration delay) if (delay == null || delay.isNegative()) throw new IllegalArgumentException("delay null or negative"); - return new BasicWebserviceCientWithRetryImpl(this, RETRY_FOREVER, delay); + return new BasicWebserviceClientWithRetryImpl(this, RETRY_FOREVER, delay); } } diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/BundleIntegrationTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/BundleIntegrationTest.java index 72d44fd95..ad1a3effe 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/BundleIntegrationTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/BundleIntegrationTest.java @@ -76,7 +76,7 @@ public void testCreateBundle() throws Exception logger.debug(fhirContext.newJsonParser().encodeResourceToString(allowList)); - Bundle updatedBundle = getWebserviceClient().updateConditionaly(allowList, + Bundle updatedBundle = getWebserviceClient().updateConditionally(allowList, Map.of("identifier", List.of("http://dsf.dev/fhir/CodeSystem/update-allow-list|allow_list"))); assertNotNull(updatedBundle); @@ -90,7 +90,7 @@ public void testCreateBundleReturnMinimal() throws Exception logger.debug(fhirContext.newJsonParser().encodeResourceToString(allowList)); - IdType id = getWebserviceClient().withMinimalReturn().updateConditionaly(allowList, + IdType id = getWebserviceClient().withMinimalReturn().updateConditionally(allowList, Map.of("identifier", List.of("http://dsf.dev/fhir/CodeSystem/update-allow-list|allow_list"))); assertNotNull(id); @@ -104,7 +104,7 @@ public void testCreateBundleReturnOperationOutcome() throws Exception logger.debug(fhirContext.newJsonParser().encodeResourceToString(allowList)); - OperationOutcome outcome = getWebserviceClient().withOperationOutcomeReturn().updateConditionaly(allowList, + OperationOutcome outcome = getWebserviceClient().withOperationOutcomeReturn().updateConditionally(allowList, Map.of("identifier", List.of("http://dsf.dev/fhir/CodeSystem/update-allow-list|allow_list"))); assertNotNull(outcome); diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationIntegrationTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationIntegrationTest.java index 556f8e2b1..f89f567b7 100755 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationIntegrationTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationIntegrationTest.java @@ -307,7 +307,7 @@ public void testIllegalConditionalUpdateWithId() throws Exception { Organization o = prepareForTestIllegalUpdate(); - expectForbidden(() -> getWebserviceClient().updateConditionaly(o, + expectForbidden(() -> getWebserviceClient().updateConditionally(o, Map.of("_id", List.of(o.getIdElement().getIdPart())))); } @@ -316,7 +316,7 @@ public void testIllegalConditionalUpdateWithoutId() throws Exception { Organization o = prepareForTestIllegalUpdate(); - expectForbidden(() -> getWebserviceClient().updateConditionaly(o, + expectForbidden(() -> getWebserviceClient().updateConditionally(o, Map.of("_id", List.of(o.getIdElement().getIdPart())))); } diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/TaskIntegrationTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/TaskIntegrationTest.java index 83853b7cf..713892a88 100755 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/TaskIntegrationTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/TaskIntegrationTest.java @@ -2416,7 +2416,7 @@ public void testIllegalConditionalUpdateWithId() throws Exception { Task created = prepareForTestIllegalUpdate(); - expectForbidden(() -> getWebserviceClient().updateConditionaly(created, + expectForbidden(() -> getWebserviceClient().updateConditionally(created, Map.of("_id", List.of(created.getIdElement().getIdPart())))); } @@ -2428,7 +2428,7 @@ public void testIllegalConditionalUpdateWithoutId() throws Exception created.setIdElement(null); created.getMeta().setVersionId(null); - expectForbidden(() -> getWebserviceClient().updateConditionaly(created, Map.of("_id", List.of(id)))); + expectForbidden(() -> getWebserviceClient().updateConditionally(created, Map.of("_id", List.of(id)))); } @Test diff --git a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceClient.java b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceClient.java index ed687e375..203c40f77 100644 --- a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceClient.java +++ b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceClient.java @@ -30,7 +30,7 @@ public interface BasicFhirWebserviceClient extends PreferReturnResource { void delete(Class resourceClass, String id); - void deleteConditionaly(Class resourceClass, Map> criteria); + void deleteConditionally(Class resourceClass, Map> criteria); void deletePermanently(Class resourceClass, String id); diff --git a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceCientWithRetryImpl.java b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceClientWithRetryImpl.java similarity index 88% rename from dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceCientWithRetryImpl.java rename to dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceClientWithRetryImpl.java index de0d8c139..718cfd4bb 100644 --- a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceCientWithRetryImpl.java +++ b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceClientWithRetryImpl.java @@ -29,18 +29,18 @@ import jakarta.ws.rs.core.MediaType; -class BasicFhirWebserviceCientWithRetryImpl extends AbstractFhirWebserviceClientJerseyWithRetry +class BasicFhirWebserviceClientWithRetryImpl extends AbstractFhirWebserviceClientJerseyWithRetry implements BasicFhirWebserviceClient { - BasicFhirWebserviceCientWithRetryImpl(FhirWebserviceClientJersey delegate, int nTimes, Duration delay) + BasicFhirWebserviceClientWithRetryImpl(FhirWebserviceClientJersey delegate, int nTimes, Duration delay) { super(delegate, nTimes, delay); } @Override - public R updateConditionaly(R resource, Map> criteria) + public R updateConditionally(R resource, Map> criteria) { - return retry(() -> delegate.updateConditionaly(resource, criteria)); + return retry(() -> delegate.updateConditionally(resource, criteria)); } @Override @@ -62,9 +62,9 @@ public Bundle postBundle(Bundle bundle) } @Override - public R createConditionaly(R resource, String ifNoneExistCriteria) + public R createConditionally(R resource, String ifNoneExistCriteria) { - return retry(() -> delegate.createConditionaly(resource, ifNoneExistCriteria)); + return retry(() -> delegate.createConditionally(resource, ifNoneExistCriteria)); } @Override @@ -201,11 +201,11 @@ public void deletePermanently(Class resourceClass, String id } @Override - public void deleteConditionaly(Class resourceClass, Map> criteria) + public void deleteConditionally(Class resourceClass, Map> criteria) { retry(() -> { - delegate.deleteConditionaly(resourceClass, criteria); + delegate.deleteConditionally(resourceClass, criteria); return null; }); } diff --git a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/FhirWebserviceClientJersey.java b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/FhirWebserviceClientJersey.java index 897eae502..b0e2a6713 100755 --- a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/FhirWebserviceClientJersey.java +++ b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/FhirWebserviceClientJersey.java @@ -187,7 +187,7 @@ PreferReturn create(PreferReturnType returnType, Resource resource) throw handleError(response); } - PreferReturn createConditionaly(PreferReturnType returnType, Resource resource, String ifNoneExistCriteria) + PreferReturn createConditionally(PreferReturnType returnType, Resource resource, String ifNoneExistCriteria) { Objects.requireNonNull(returnType, "returnType"); Objects.requireNonNull(resource, "resource"); @@ -250,7 +250,7 @@ PreferReturn update(PreferReturnType returnType, Resource resource) throw handleError(response); } - PreferReturn updateConditionaly(PreferReturnType returnType, Resource resource, Map> criteria) + PreferReturn updateConditionally(PreferReturnType returnType, Resource resource, Map> criteria) { Objects.requireNonNull(returnType, "returnType"); Objects.requireNonNull(resource, "resource"); @@ -326,9 +326,9 @@ public R create(R resource) @Override @SuppressWarnings("unchecked") - public R createConditionaly(R resource, String ifNoneExistCriteria) + public R createConditionally(R resource, String ifNoneExistCriteria) { - return (R) createConditionaly(PreferReturnType.REPRESENTATION, resource, ifNoneExistCriteria).getResource(); + return (R) createConditionally(PreferReturnType.REPRESENTATION, resource, ifNoneExistCriteria).getResource(); } @Override @@ -347,9 +347,9 @@ public R update(R resource) @Override @SuppressWarnings("unchecked") - public R updateConditionaly(R resource, Map> criteria) + public R updateConditionally(R resource, Map> criteria) { - return (R) updateConditionaly(PreferReturnType.REPRESENTATION, resource, criteria).getResource(); + return (R) updateConditionally(PreferReturnType.REPRESENTATION, resource, criteria).getResource(); } @Override @@ -384,7 +384,7 @@ public void delete(Class resourceClass, String id) } @Override - public void deleteConditionaly(Class resourceClass, Map> criteria) + public void deleteConditionally(Class resourceClass, Map> criteria) { Objects.requireNonNull(resourceClass, "resourceClass"); Objects.requireNonNull(criteria, "criteria"); @@ -897,7 +897,7 @@ public BasicFhirWebserviceClient withRetry(int nTimes, Duration delay) if (delay == null || delay.isNegative()) throw new IllegalArgumentException("delay null or negative"); - return new BasicFhirWebserviceCientWithRetryImpl(this, nTimes, delay); + return new BasicFhirWebserviceClientWithRetryImpl(this, nTimes, delay); } @Override @@ -906,7 +906,7 @@ public BasicFhirWebserviceClient withRetryForever(Duration delay) if (delay == null || delay.isNegative()) throw new IllegalArgumentException("delay null or negative"); - return new BasicFhirWebserviceCientWithRetryImpl(this, RETRY_FOREVER, delay); + return new BasicFhirWebserviceClientWithRetryImpl(this, RETRY_FOREVER, delay); } @Override diff --git a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnMinimal.java b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnMinimal.java index 4bceee1ae..7604706fa 100644 --- a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnMinimal.java +++ b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnMinimal.java @@ -29,13 +29,13 @@ public interface PreferReturnMinimal { IdType create(Resource resource); - IdType createConditionaly(Resource resource, String ifNoneExistCriteria); + IdType createConditionally(Resource resource, String ifNoneExistCriteria); IdType createBinary(InputStream in, MediaType mediaType, String securityContextReference); IdType update(Resource resource); - IdType updateConditionaly(Resource resource, Map> criteria); + IdType updateConditionally(Resource resource, Map> criteria); IdType updateBinary(String id, InputStream in, MediaType mediaType, String securityContextReference); diff --git a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnMinimalRetryImpl.java b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnMinimalRetryImpl.java index de4167028..133b54196 100644 --- a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnMinimalRetryImpl.java +++ b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnMinimalRetryImpl.java @@ -41,10 +41,10 @@ public IdType create(Resource resource) } @Override - public IdType createConditionaly(Resource resource, String ifNoneExistCriteria) + public IdType createConditionally(Resource resource, String ifNoneExistCriteria) { return retry( - () -> delegate.createConditionaly(PreferReturnType.MINIMAL, resource, ifNoneExistCriteria).getId()); + () -> delegate.createConditionally(PreferReturnType.MINIMAL, resource, ifNoneExistCriteria).getId()); } @Override @@ -61,9 +61,9 @@ public IdType update(Resource resource) } @Override - public IdType updateConditionaly(Resource resource, Map> criteria) + public IdType updateConditionally(Resource resource, Map> criteria) { - return retry(() -> delegate.updateConditionaly(PreferReturnType.MINIMAL, resource, criteria).getId()); + return retry(() -> delegate.updateConditionally(PreferReturnType.MINIMAL, resource, criteria).getId()); } @Override diff --git a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnMinimalWithRetryImpl.java b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnMinimalWithRetryImpl.java index 4935059b6..e74ba6d5b 100644 --- a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnMinimalWithRetryImpl.java +++ b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnMinimalWithRetryImpl.java @@ -43,9 +43,9 @@ public IdType create(Resource resource) } @Override - public IdType createConditionaly(Resource resource, String ifNoneExistCriteria) + public IdType createConditionally(Resource resource, String ifNoneExistCriteria) { - return delegate.createConditionaly(PreferReturnType.MINIMAL, resource, ifNoneExistCriteria).getId(); + return delegate.createConditionally(PreferReturnType.MINIMAL, resource, ifNoneExistCriteria).getId(); } @Override @@ -61,9 +61,9 @@ public IdType update(Resource resource) } @Override - public IdType updateConditionaly(Resource resource, Map> criteria) + public IdType updateConditionally(Resource resource, Map> criteria) { - return delegate.updateConditionaly(PreferReturnType.MINIMAL, resource, criteria).getId(); + return delegate.updateConditionally(PreferReturnType.MINIMAL, resource, criteria).getId(); } @Override diff --git a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnOutcome.java b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnOutcome.java index cb65eb1e3..d023c7629 100644 --- a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnOutcome.java +++ b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnOutcome.java @@ -29,14 +29,14 @@ public interface PreferReturnOutcome { OperationOutcome create(Resource resource); - OperationOutcome createConditionaly(Resource resource, String ifNoneExistCriteria); + OperationOutcome createConditionally(Resource resource, String ifNoneExistCriteria); OperationOutcome createBinary(InputStream in, MediaType mediaType, String securityContextReference); OperationOutcome update(Resource resource); - OperationOutcome updateConditionaly(Resource resource, Map> criteria); + OperationOutcome updateConditionally(Resource resource, Map> criteria); OperationOutcome updateBinary(String id, InputStream in, MediaType mediaType, String securityContextReference); diff --git a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnOutcomeRetryImpl.java b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnOutcomeRetryImpl.java index 5fee79b67..f10e630d7 100644 --- a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnOutcomeRetryImpl.java +++ b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnOutcomeRetryImpl.java @@ -41,10 +41,10 @@ public OperationOutcome create(Resource resource) } @Override - public OperationOutcome createConditionaly(Resource resource, String ifNoneExistCriteria) + public OperationOutcome createConditionally(Resource resource, String ifNoneExistCriteria) { return retry( - () -> delegate.createConditionaly(PreferReturnType.OPERATION_OUTCOME, resource, ifNoneExistCriteria) + () -> delegate.createConditionally(PreferReturnType.OPERATION_OUTCOME, resource, ifNoneExistCriteria) .getOperationOutcome()); } @@ -63,9 +63,9 @@ public OperationOutcome update(Resource resource) } @Override - public OperationOutcome updateConditionaly(Resource resource, Map> criteria) + public OperationOutcome updateConditionally(Resource resource, Map> criteria) { - return retry(() -> delegate.updateConditionaly(PreferReturnType.OPERATION_OUTCOME, resource, criteria) + return retry(() -> delegate.updateConditionally(PreferReturnType.OPERATION_OUTCOME, resource, criteria) .getOperationOutcome()); } diff --git a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnOutcomeWithRetryImpl.java b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnOutcomeWithRetryImpl.java index d3854d40d..9fc54bfbb 100644 --- a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnOutcomeWithRetryImpl.java +++ b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnOutcomeWithRetryImpl.java @@ -43,9 +43,9 @@ public OperationOutcome create(Resource resource) } @Override - public OperationOutcome createConditionaly(Resource resource, String ifNoneExistCriteria) + public OperationOutcome createConditionally(Resource resource, String ifNoneExistCriteria) { - return delegate.createConditionaly(PreferReturnType.OPERATION_OUTCOME, resource, ifNoneExistCriteria) + return delegate.createConditionally(PreferReturnType.OPERATION_OUTCOME, resource, ifNoneExistCriteria) .getOperationOutcome(); } @@ -63,9 +63,9 @@ public OperationOutcome update(Resource resource) } @Override - public OperationOutcome updateConditionaly(Resource resource, Map> criteria) + public OperationOutcome updateConditionally(Resource resource, Map> criteria) { - return delegate.updateConditionaly(PreferReturnType.OPERATION_OUTCOME, resource, criteria) + return delegate.updateConditionally(PreferReturnType.OPERATION_OUTCOME, resource, criteria) .getOperationOutcome(); } diff --git a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnResource.java b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnResource.java index a02f3a764..818272582 100644 --- a/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnResource.java +++ b/dsf-fhir/dsf-fhir-webservice-client/src/main/java/dev/dsf/fhir/client/PreferReturnResource.java @@ -29,14 +29,14 @@ public interface PreferReturnResource { R create(R resource); - R createConditionaly(R resource, String ifNoneExistCriteria); + R createConditionally(R resource, String ifNoneExistCriteria); Binary createBinary(InputStream in, MediaType mediaType, String securityContextReference); R update(R resource); - R updateConditionaly(R resource, Map> criteria); + R updateConditionally(R resource, Map> criteria); Binary updateBinary(String id, InputStream in, MediaType mediaType, String securityContextReference); From e55f2cb910452256cb1a44c534d8bc54c9f48ad4 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 21:12:00 +0200 Subject: [PATCH 20/56] typo fix Cient -> Client No method name changes in API interfaces and classes --- ...yImpl.java => BasicFhirWebserviceClientWithRetryImpl.java} | 4 ++-- .../java/dev/dsf/fhir/client/FhirWebserviceClientJersey.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/{BasicFhirWebserviceCientWithRetryImpl.java => BasicFhirWebserviceClientWithRetryImpl.java} (96%) diff --git a/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceCientWithRetryImpl.java b/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceClientWithRetryImpl.java similarity index 96% rename from dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceCientWithRetryImpl.java rename to dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceClientWithRetryImpl.java index e9d4df7da..f6a3eea49 100644 --- a/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceCientWithRetryImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/BasicFhirWebserviceClientWithRetryImpl.java @@ -29,10 +29,10 @@ import jakarta.ws.rs.core.MediaType; -class BasicFhirWebserviceCientWithRetryImpl extends AbstractFhirWebserviceClientJerseyWithRetry +class BasicFhirWebserviceClientWithRetryImpl extends AbstractFhirWebserviceClientJerseyWithRetry implements BasicFhirWebserviceClient { - BasicFhirWebserviceCientWithRetryImpl(FhirWebserviceClientJersey delegate, int nTimes, Duration delay) + BasicFhirWebserviceClientWithRetryImpl(FhirWebserviceClientJersey delegate, int nTimes, Duration delay) { super(delegate, nTimes, delay); } diff --git a/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/FhirWebserviceClientJersey.java b/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/FhirWebserviceClientJersey.java index 27179f294..f25ede027 100644 --- a/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/FhirWebserviceClientJersey.java +++ b/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/FhirWebserviceClientJersey.java @@ -747,7 +747,7 @@ public BasicFhirWebserviceClient withRetry(int nTimes, long delayMillis) if (delayMillis < 0) throw new IllegalArgumentException("delayMillis < 0"); - return new BasicFhirWebserviceCientWithRetryImpl(this, nTimes, Duration.ofMillis(delayMillis)); + return new BasicFhirWebserviceClientWithRetryImpl(this, nTimes, Duration.ofMillis(delayMillis)); } @Override @@ -756,7 +756,7 @@ public BasicFhirWebserviceClient withRetryForever(long delayMillis) if (delayMillis < 0) throw new IllegalArgumentException("delayMillis < 0"); - return new BasicFhirWebserviceCientWithRetryImpl(this, RETRY_FOREVER, Duration.ofMillis(delayMillis)); + return new BasicFhirWebserviceClientWithRetryImpl(this, RETRY_FOREVER, Duration.ofMillis(delayMillis)); } @Override From 1051addc1d753b00e96786ed69af0ae6361ee467 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 21:15:57 +0200 Subject: [PATCH 21/56] typo fix Allready -> Already --- .../dev/dsf/fhir/integration/SubscriptionIntegrationTest.java | 4 ++-- .../main/java/dev/dsf/fhir/client/WebsocketClientTyrus.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/SubscriptionIntegrationTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/SubscriptionIntegrationTest.java index 34652e087..204dce75a 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/SubscriptionIntegrationTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/SubscriptionIntegrationTest.java @@ -77,7 +77,7 @@ public void testCreateOkNoPayload() throws Exception } @Test - public void testCreateOkNoPayloadAllreadyExistsWithPayload() throws Exception + public void testCreateOkNoPayloadAlreadyExistsWithPayload() throws Exception { Subscription t = newSubscription("Task?status=completed"); @@ -94,7 +94,7 @@ public void testCreateOkNoPayloadAllreadyExistsWithPayload() throws Exception } @Test - public void testCreateNotOkNoPayloadAllreadyExistsWithoutPayload() throws Exception + public void testCreateNotOkNoPayloadAlreadyExistsWithoutPayload() throws Exception { Subscription t = newSubscription("Task?status=completed"); t.getChannel().setPayload(null); diff --git a/dsf-fhir/dsf-fhir-websocket-client/src/main/java/dev/dsf/fhir/client/WebsocketClientTyrus.java b/dsf-fhir/dsf-fhir-websocket-client/src/main/java/dev/dsf/fhir/client/WebsocketClientTyrus.java index 8e5373077..73722dea2 100755 --- a/dsf-fhir/dsf-fhir-websocket-client/src/main/java/dev/dsf/fhir/client/WebsocketClientTyrus.java +++ b/dsf-fhir/dsf-fhir-websocket-client/src/main/java/dev/dsf/fhir/client/WebsocketClientTyrus.java @@ -144,7 +144,7 @@ private ClientEndpoint createClientEndpoint(Runnable reconnector, String subscri public void connect() { if (manager != null) - throw new IllegalStateException("Allready connecting/connected"); + throw new IllegalStateException("Already connecting/connected"); closed = false; From b1f71b759915f29947a6316c3dab4daa81046180 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 21:16:43 +0200 Subject: [PATCH 22/56] typo fix Keys -> keys --- .../src/main/java/dev/dsf/maven/dev/FileRemover.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/dev/FileRemover.java b/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/dev/FileRemover.java index 7c77df68e..7e968a045 100644 --- a/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/dev/FileRemover.java +++ b/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/dev/FileRemover.java @@ -165,10 +165,10 @@ private Path toKeyPath(String commonName, String postFix) return keyDir.resolve(commonName.replaceAll(" ", "_") + postFix); } - public void deleteFilesInKeyDir(List Keys) + public void deleteFilesInKeyDir(List keys) { - Stream idsToDelete = Keys == null ? Stream.empty() - : Keys.stream().map(Key::getId).filter(Objects::nonNull); + Stream idsToDelete = keys == null ? Stream.empty() + : keys.stream().map(Key::getId).filter(Objects::nonNull); idsToDelete.forEach(id -> { From 3dd62d81253643f7c9ce59ed805002ae0ffa3494 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 21:21:00 +0200 Subject: [PATCH 23/56] renamed class: ValidationWrapperExtension -> ValidatorWrapperExtension --- .../validator/FhirInstanceValidatorExtension.java | 2 +- ...WrapperExtension.java => ValidatorWrapperExtension.java} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename dsf-fhir/dsf-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/{ValidationWrapperExtension.java => ValidatorWrapperExtension.java} (88%) diff --git a/dsf-fhir/dsf-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidatorExtension.java b/dsf-fhir/dsf-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidatorExtension.java index 3e3db7255..2b5e6c54b 100644 --- a/dsf-fhir/dsf-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidatorExtension.java +++ b/dsf-fhir/dsf-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidatorExtension.java @@ -41,6 +41,6 @@ public FhirInstanceValidatorExtension(IValidationSupport validationSupport, @Override protected List validate(IValidationContext validationContext) { - return ValidationWrapperExtension.create(resourceFetcher).validate(workerContext, validationContext); + return ValidatorWrapperExtension.create(resourceFetcher).validate(workerContext, validationContext); } } diff --git a/dsf-fhir/dsf-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidationWrapperExtension.java b/dsf-fhir/dsf-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapperExtension.java similarity index 88% rename from dsf-fhir/dsf-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidationWrapperExtension.java rename to dsf-fhir/dsf-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapperExtension.java index 77bd2f21e..3a5a7624a 100644 --- a/dsf-fhir/dsf-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidationWrapperExtension.java +++ b/dsf-fhir/dsf-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapperExtension.java @@ -20,15 +20,15 @@ import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; -public class ValidationWrapperExtension extends ValidatorWrapper +public class ValidatorWrapperExtension extends ValidatorWrapper { - public ValidationWrapperExtension() + public ValidatorWrapperExtension() { } public static ValidatorWrapper create(IValidatorResourceFetcher validatorResourceFetcher) { - return new ValidationWrapperExtension().setAnyExtensionsAllowed(true) + return new ValidatorWrapperExtension().setAnyExtensionsAllowed(true) .setBestPracticeWarningLevel(BestPracticeWarningLevel.Ignore).setErrorForUnknownProfiles(true) .setExtensionDomains(List.of()).setValidationPolicyAdvisor(new FhirDefaultPolicyAdvisor()) .setNoTerminologyChecks(false).setNoExtensibleWarnings(false).setNoBindingMsgSuppressed(false) From a278e2e821e04f7db4a011abd85e62a4638163e9 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 4 Aug 2026 21:21:24 +0200 Subject: [PATCH 24/56] typo fix Unsupportet -> Unsupported --- .../java/dev/dsf/bpe/subscription/LocalFhirConnectorImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/subscription/LocalFhirConnectorImpl.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/subscription/LocalFhirConnectorImpl.java index bf42c99b1..6ccd7f743 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/subscription/LocalFhirConnectorImpl.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/subscription/LocalFhirConnectorImpl.java @@ -300,7 +300,7 @@ private EventType toEventType(String payload) case Constants.CT_FHIR_JSON, Constants.CT_FHIR_JSON_NEW -> EventType.JSON; case Constants.CT_FHIR_XML, Constants.CT_FHIR_XML_NEW -> EventType.XML; - default -> throw new RuntimeException("Unsupportet subscription.payload " + payload); + default -> throw new RuntimeException("Unsupported subscription.payload " + payload); }; } From 66104f422e63141a92a4b2370f89d8829b54b21d Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Wed, 12 Aug 2026 23:55:56 +0200 Subject: [PATCH 25/56] improved readability: set empty string replaced with unset --- dsf-docker/bpe_proxy/conf/extra/host-ssl.conf | 2 +- dsf-docker/fhir_proxy/conf/extra/host-ssl.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-docker/bpe_proxy/conf/extra/host-ssl.conf b/dsf-docker/bpe_proxy/conf/extra/host-ssl.conf index 1416b45e7..01e40ed83 100644 --- a/dsf-docker/bpe_proxy/conf/extra/host-ssl.conf +++ b/dsf-docker/bpe_proxy/conf/extra/host-ssl.conf @@ -31,7 +31,7 @@ SSLVerifyDepth 3 SSLVerifyClient "${SSL_VERIFY_CLIENT}" SSLOptions +ExportCertData -RequestHeader set X-ClientCert "" +RequestHeader unset X-ClientCert Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains" diff --git a/dsf-docker/fhir_proxy/conf/extra/host-ssl.conf b/dsf-docker/fhir_proxy/conf/extra/host-ssl.conf index 242473360..7a05268e9 100755 --- a/dsf-docker/fhir_proxy/conf/extra/host-ssl.conf +++ b/dsf-docker/fhir_proxy/conf/extra/host-ssl.conf @@ -45,7 +45,7 @@ SSLVerifyDepth 3 SSLVerifyClient "${SSL_VERIFY_CLIENT}" SSLOptions +ExportCertData -RequestHeader set X-ClientCert "" +RequestHeader unset X-ClientCert Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains" From b9ccb248bae7b19a5fa2512c56754bb6fd31d04e Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Thu, 13 Aug 2026 02:34:17 +0200 Subject: [PATCH 26/56] adds trusted reverse proxy allow-list feature - Default config sets hostname "proxy" as allowed reverse proxy. - The feature can be disabled by configuring "disabled" as the only allowed reverse proxy. - Hostnames, IPv4/IPv6 addresses and IPv4/IPv6 CIDR networks can be configured as allowed. - Hostnames are resolved against the DNS and periodically refreshed. The timeout can be configured and should be chosen according to the acceptable stale-authorization window and e.g. the expected reverse-proxy replacement time. --- .../bpe/spring/config/PropertiesConfig.java | 4 +- ...=> AbstractCertificateAndProxyConfig.java} | 21 +- .../network/DynamicHostnameMatcher.java | 165 +++++ .../common/config/network/HostSpecParser.java | 594 ++++++++++++++++++ .../network/InetSocketAddressMatcher.java | 38 ++ .../network/InetSocketAddressMatcherList.java | 79 +++ .../config/network/StaticCidrMatcher.java | 125 ++++ .../network/DynamicHostnameMatcherTest.java | 185 ++++++ .../config/network/HostSpecParserTest.java | 308 +++++++++ .../InetSocketAddressMatcherListTest.java | 73 +++ .../config/network/StaticCidrMatcherTest.java | 256 ++++++++ .../common/config/AbstractJettyConfig.java | 20 +- .../ForwardedSecureRequestCustomizer.java | 22 +- .../dev/dsf/common/jetty/JettyServer.java | 55 +- .../fhir/spring/config/PropertiesConfig.java | 4 +- 15 files changed, 1936 insertions(+), 13 deletions(-) rename dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/{AbstractCertificateConfig.java => AbstractCertificateAndProxyConfig.java} (94%) create mode 100644 dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/DynamicHostnameMatcher.java create mode 100644 dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/HostSpecParser.java create mode 100644 dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcher.java create mode 100644 dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcherList.java create mode 100644 dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticCidrMatcher.java create mode 100644 dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/DynamicHostnameMatcherTest.java create mode 100644 dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/HostSpecParserTest.java create mode 100644 dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/InetSocketAddressMatcherListTest.java create mode 100644 dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/StaticCidrMatcherTest.java diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java index a95ac98e4..27435d59f 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java @@ -48,7 +48,7 @@ import de.hsheilbronn.mi.utils.crypto.cert.CertificateValidator; import de.hsheilbronn.mi.utils.crypto.io.PemReader; -import dev.dsf.common.config.AbstractCertificateConfig; +import dev.dsf.common.config.AbstractCertificateAndProxyConfig; import dev.dsf.common.config.ProxyConfig; import dev.dsf.common.config.ProxyConfigImpl; import dev.dsf.common.db.migration.DbMigratorConfig; @@ -58,7 +58,7 @@ @Configuration @PropertySource(value = "file:conf/config.properties", encoding = "UTF-8", ignoreResourceNotFound = true) -public class PropertiesConfig extends AbstractCertificateConfig implements InitializingBean +public class PropertiesConfig extends AbstractCertificateAndProxyConfig implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(PropertiesConfig.class); diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/AbstractCertificateConfig.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/AbstractCertificateAndProxyConfig.java similarity index 94% rename from dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/AbstractCertificateConfig.java rename to dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/AbstractCertificateAndProxyConfig.java index aa2ad6ec1..3360519ac 100644 --- a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/AbstractCertificateConfig.java +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/AbstractCertificateAndProxyConfig.java @@ -33,9 +33,13 @@ import de.hsheilbronn.mi.utils.crypto.io.PemReader; import de.hsheilbronn.mi.utils.crypto.keypair.KeyPairValidator; import de.hsheilbronn.mi.utils.crypto.keystore.KeyStoreCreator; +import dev.dsf.common.config.network.HostSpecParser; +import dev.dsf.common.config.network.HostSpecParser.HostSpec; -public abstract class AbstractCertificateConfig +public abstract class AbstractCertificateAndProxyConfig { + public static final String HOST_SPEC_LIST_DISABLED = "disabled"; + private static final class RuntimeIOException extends RuntimeException { private static final long serialVersionUID = 1L; @@ -320,4 +324,19 @@ protected KeyStore createKeyStoreFromP12(String file, char[] keyStorePassword, S throw new RuntimeException(e); } } + + protected List parseHostSpecList(String propertyName, List values, boolean supportDisabled) + { + if (supportDisabled && values != null && values.size() == 1 && HOST_SPEC_LIST_DISABLED.equals(values.get(0))) + return List.of(); + + try + { + return HostSpecParser.parse(values); + } + catch (IllegalArgumentException e) + { + throw new IllegalArgumentException(errorMessage(propertyName, e.getMessage()), e); + } + } } diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/DynamicHostnameMatcher.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/DynamicHostnameMatcher.java new file mode 100644 index 000000000..73186fd0e --- /dev/null +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/DynamicHostnameMatcher.java @@ -0,0 +1,165 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.common.config.network; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.common.config.network.HostSpecParser.HostSpec; + +/** + * Matches resolved socket addresses against the IP addresses currently associated with a configured DNS domain. The + * domain is resolved on construction and periodically refreshed after the configured timeout; unresolved socket + * addresses are rejected and no reverse DNS lookup is performed. + * + *

+ * The refresh timeout should be chosen according to the acceptable stale-authorization window and e.g. the expected + * reverse-proxy replacement time. + *

+ *

+ * Warning: This matcher does not consider the configured {@link HostSpec#port()} + *

+ */ +public class DynamicHostnameMatcher implements InetSocketAddressMatcher +{ + private static final Logger logger = LoggerFactory.getLogger(DynamicHostnameMatcher.DnsResolver.class); + + @FunctionalInterface + public interface DnsResolver + { + InetAddress[] resolve(String domain) throws UnknownHostException; + } + + public static InetSocketAddressMatcherList of(Duration refreshTimeout, HostSpec... hostSpecs) + { + return of(refreshTimeout, hostSpecs == null ? List.of() : Arrays.asList(hostSpecs)); + } + + public static InetSocketAddressMatcherList of(Duration refreshTimeout, Collection hostSpecs) + { + return new InetSocketAddressMatcherList(hostSpecs == null ? List.of() + : hostSpecs.stream().filter(HostSpec::isDomain).map(host -> of(refreshTimeout, host)).toList()); + } + + public static DynamicHostnameMatcher of(Duration refreshTimeout, HostSpec hostSpec) + { + Objects.requireNonNull(refreshTimeout, "refreshTimeout"); + Objects.requireNonNull(hostSpec, "hostSpec"); + + return new DynamicHostnameMatcher(refreshTimeout, hostSpec.host()); + } + + private final ReentrantLock refreshLock = new ReentrantLock(); + private final Duration refreshTimeout; + + private final String hostname; + + private final Clock clock; + private final DnsResolver dnsResolver; + + private volatile Set addresses = Set.of(); + private volatile Instant expiresAt = Instant.MIN; + + private DynamicHostnameMatcher(Duration refreshTimeout, String hostname) + { + this(refreshTimeout, hostname, Clock.systemUTC(), InetAddress::getAllByName); + } + + // package private for testing + DynamicHostnameMatcher(Duration refreshTimeout, String hostname, Clock clock, DnsResolver dnsResolver) + { + Objects.requireNonNull(refreshTimeout, "refreshTimeout"); + Objects.requireNonNull(hostname, "hostname"); + + if (refreshTimeout == null || refreshTimeout.isZero() || refreshTimeout.isNegative()) + throw new IllegalArgumentException("refreshTimeout must be greater than zero"); + if (hostname.isBlank()) + throw new IllegalArgumentException("hostname must not be blank"); + + this.refreshTimeout = refreshTimeout; + + this.hostname = hostname; + + this.clock = clock; + this.dnsResolver = dnsResolver; + + refresh(); + } + + public boolean matches(InetSocketAddress address) + { + if (address == null || address.isUnresolved()) + return false; + + refreshIfExpired(); + + return addresses.contains(address.getAddress()); + } + + private void refreshIfExpired() + { + if (clock.instant().isBefore(expiresAt)) + return; + + refresh(); + } + + private void refresh() + { + refreshLock.lock(); + try + { + if (clock.instant().isBefore(expiresAt)) + return; + + try + { + addresses = Set.copyOf(List.of(dnsResolver.resolve(hostname))); + } + catch (UnknownHostException | RuntimeException e) + { + logger.warn("Unable to resolve domain '{}': {} - {}", hostname, e.getClass().getName(), e.getMessage()); + + // DNS failure means no address is authorized + addresses = Set.of(); + } + expiresAt = clock.instant().plus(refreshTimeout); + } + finally + { + refreshLock.unlock(); + } + } + + @Override + public String toString() + { + return hostname; + } +} \ No newline at end of file diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/HostSpecParser.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/HostSpecParser.java new file mode 100644 index 000000000..3f41a35a0 --- /dev/null +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/HostSpecParser.java @@ -0,0 +1,594 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.common.config.network; + +import java.net.IDN; +import java.net.Inet6Address; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; + +/** + * + * Parses host specifications into immutable {@link HostSpec} instances. + * + *

+ * A host specification may represent a domain name, a wildcard domain, an IPv4 address, an IPv4 CIDR network, an IPv6 + * address, or an IPv6 CIDR network. All host types may specify an optional port, except that IPv6 addresses must always + * be enclosed in square brackets when a port is present (and are required to use brackets in all cases). + * + *

+ * Examples of supported specifications: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Supported host specification formats
KindExampleExample with port
{@link Kind#DOMAIN DOMAIN}{@code example.com}{@code example.com:443}
{@link Kind#DOMAIN_WILDCARD_SINGLE_LEVEL WILDCARD_DOMAIN_ONE_LEVEL}{@code *.example.com}{@code *.example.com:443}
{@link Kind#DOMAIN_WILDCARD_MULTI_LEVEL WILDCARD_DOMAIN_MULTI_LEVEL}{@code **.example.com}{@code **.example.com:443}
{@link Kind#IPV4 IPV4}{@code 192.0.2.1}{@code 192.0.2.1:443}
{@link Kind#IPV4_CIDR IPV4_CIDR}{@code 192.0.2.0/24}{@code 192.0.2.0/24:443}
{@link Kind#IPV6 IPV6}{@code [2001:db8::1]}{@code [2001:db8::1]:443}
{@link Kind#IPV6_CIDR IPV6_CIDR}{@code [2001:db8::/32]}{@code [2001:db8::/32]:443}
+ * + *

+ * Ports are optional and must be decimal values in the range {@code 0..65535}. + * + *

+ * IPv6 specifications are always enclosed in square brackets, including IPv6 specifications without a port. This + * removes ambiguity between the IPv6 address's colons and the optional port separator. + * + *

+ * Domain names support Unicode/IDN labels according to the behavior of {@link java.net.IDN}. The original Unicode + * representation supplied by the caller is retained in {@link HostSpec#host()}; IDN conversion is used for validation + * and does not replace the original host value. + * + *

+ * For example: + * + *

{@code
+ *
+ * HostSpecParser.parse("example.com:443");
+ * HostSpecParser.parse("münchen.example:443");
+ * HostSpecParser.parse("*.example.com");
+ * HostSpecParser.parse("**.example.com:8443");
+ * HostSpecParser.parse("192.0.2.1");
+ * HostSpecParser.parse("192.0.2.0/24:8080");
+ * HostSpecParser.parse("[2001:db8::1]");
+ * HostSpecParser.parse("[2001:db8::1]:443");
+ * HostSpecParser.parse("[2001:db8::/32]:8443");
+ * }
+ * + *

+ * The parser trims leading and trailing whitespace from the supplied specification. A {@link NullPointerException} is + * thrown for a {@code null} input, and an {@link IllegalArgumentException} is thrown for an empty or syntactically + * invalid specification. + * + * @see HostSpec + * @see Kind + * @see java.net.IDN + */ +public final class HostSpecParser +{ + private static final int MAX_PORT = 65_535; + private static final int MAX_IPV4_PREFIX = 32; + private static final int MAX_IPV6_PREFIX = 128; + + private HostSpecParser() + { + } + + /** + * @param inputs + * may be null, null or blank elements are filtered + * @return + * @throws IllegalArgumentException + */ + public static List parse(String... inputs) + { + return inputs == null ? List.of() : parse(Arrays.asList(inputs)); + } + + /** + * @param inputs + * may be {@link NullPointerException}, null or blank elements are filtered + * @return + * @throws IllegalArgumentException + */ + public static List parse(Collection inputs) + { + return inputs == null ? List.of() + : inputs.stream().filter(Objects::nonNull).filter(Predicate.not(String::isBlank)) + .map(HostSpecParser::parse).toList(); + } + + /** + * @param input + * not null, not blank + * @return + * @throws IllegalArgumentException + */ + public static HostSpec parse(String input) + { + Objects.requireNonNull(input, "input"); + if (input.isBlank()) + throw new IllegalArgumentException("input is blank"); + + String value = input.trim(); + + if (value.isEmpty()) + throw invalid(input, "Host specification is empty"); + + if (value.startsWith("[")) + return parseIpv6(value); + + if (value.startsWith("**.")) + return parseWildcard(value, true); + + if (value.startsWith("*.")) + return parseWildcard(value, false); + + if (looksLikeIpv4(value)) + return parseIpv4(value); + + return parseDomain(value); + } + + private static HostSpec parseIpv6(String input) + { + int closingBracket = input.indexOf(']'); + + if (closingBracket < 0) + throw invalid(input, "Missing closing ']' for IPv6 address"); + + String inside = input.substring(1, closingBracket); + String after = input.substring(closingBracket + 1); + + if (inside.isEmpty()) + throw invalid(input, "IPv6 address is empty"); + + Integer port = null; + + if (!after.isEmpty()) + { + if (!after.startsWith(":")) + throw invalid(input, "Unexpected characters after IPv6 address"); + + port = parsePort(after.substring(1), input); + } + + int slash = inside.indexOf('/'); + + if (slash >= 0) + { + if (inside.indexOf('/', slash + 1) >= 0) + throw invalid(input, "Invalid IPv6 CIDR specification"); + + String address = inside.substring(0, slash); + String prefixText = inside.substring(slash + 1); + + if (address.isEmpty()) + throw invalid(input, "IPv6 address is empty"); + + int prefix = parseInteger(prefixText, input, "IPv6 prefix length"); + + if (prefix > MAX_IPV6_PREFIX) + throw invalid(input, "IPv6 prefix length must be 0..128"); + + if (!isValidIpv6(address)) + throw invalid(input, "Invalid IPv6 address"); + + return new HostSpec(Kind.IPV6_CIDR, address, prefix, port); + } + + if (!isValidIpv6(inside)) + throw invalid(input, "Invalid IPv6 address"); + + return new HostSpec(Kind.IPV6, inside, MAX_IPV6_PREFIX, port); + } + + private static HostSpec parseWildcard(String input, boolean multiLevel) + { + String prefix = multiLevel ? "**." : "*."; + String remainder = input.substring(prefix.length()); + + HostAndPort hostAndPort = splitHostAndPort(remainder); + + if (!isValidDomain(hostAndPort.host())) + throw invalid(input, "Invalid wildcard domain"); + + Kind kind = multiLevel ? Kind.DOMAIN_WILDCARD_MULTI_LEVEL : Kind.DOMAIN_WILDCARD_SINGLE_LEVEL; + + return new HostSpec(kind, hostAndPort.host(), null, hostAndPort.port()); + } + + private static boolean looksLikeIpv4(String input) + { + String host = input; + + int colon = host.lastIndexOf(':'); + + if (colon >= 0) + host = host.substring(0, colon); + + int slash = host.indexOf('/'); + + if (slash >= 0) + host = host.substring(0, slash); + + return isAsciiIpv4Candidate(host); + } + + private static boolean isAsciiIpv4Candidate(String value) + { + if (value.isEmpty()) + return false; + + int dots = 0; + + for (int i = 0; i < value.length(); i++) + { + char c = value.charAt(i); + + if (c == '.') + dots++; + else if (c < '0' || c > '9') + return false; + } + + return dots == 3; + } + + private static HostSpec parseIpv4(String input) + { + HostAndPort hostAndPort = splitHostAndPort(input); + String host = hostAndPort.host(); + + int slash = host.indexOf('/'); + + if (slash >= 0) + { + if (host.indexOf('/', slash + 1) >= 0) + throw invalid(input, "Invalid IPv4 CIDR specification"); + + String address = host.substring(0, slash); + String prefixText = host.substring(slash + 1); + + int prefix = parseInteger(prefixText, input, "IPv4 prefix length"); + + if (prefix > MAX_IPV4_PREFIX) + throw invalid(input, "IPv4 prefix length must be 0..32"); + + if (!isValidIpv4(address)) + throw invalid(input, "Invalid IPv4 address"); + + return new HostSpec(Kind.IPV4_CIDR, address, prefix, hostAndPort.port()); + } + + if (!isValidIpv4(host)) + throw invalid(input, "Invalid IPv4 address"); + + return new HostSpec(Kind.IPV4, host, MAX_IPV4_PREFIX, hostAndPort.port()); + } + + private static HostSpec parseDomain(String input) + { + HostAndPort hostAndPort = splitHostAndPort(input); + + if (!isValidDomain(hostAndPort.host())) + throw invalid(input, "Invalid domain"); + + return new HostSpec(Kind.DOMAIN, hostAndPort.host(), null, hostAndPort.port()); + } + + /** + * Splits a hostname from its optional ":port". + * + * IPv6 is handled separately because it must be bracketed. + */ + private static HostAndPort splitHostAndPort(String input) + { + int colon = input.lastIndexOf(':'); + + if (colon < 0) + return new HostAndPort(input, null); + + String host = input.substring(0, colon); + String portText = input.substring(colon + 1); + + if (host.isEmpty()) + throw invalid(input, "Missing host"); + + /* + * An unbracketed host containing another colon cannot be a domain or IPv4 address. IPv6 must have been handled + * by parseIpv6() already. + */ + if (host.indexOf(':') >= 0) + throw invalid(input, "IPv6 addresses must be bracketed"); + + return new HostAndPort(host, parsePort(portText, input)); + } + + private static int parsePort(String text, String input) + { + int port = parseInteger(text, input, "port"); + + if (port < 0 || port > MAX_PORT) + throw invalid(input, "Port must be 0..65535"); + + return port; + } + + /** + * Parses an ASCII decimal integer. + * + * Unicode decimal digits are intentionally rejected for ports and CIDR prefix lengths. + */ + private static int parseInteger(String text, String input, String name) + { + if (text.isEmpty()) + throw invalid(input, "Invalid " + name); + + for (int i = 0; i < text.length(); i++) + { + char c = text.charAt(i); + + if (c < '0' || c > '9') + throw invalid(input, "Invalid " + name); + } + + try + { + return Integer.parseInt(text); + } + catch (NumberFormatException e) + { + throw invalid(input, "Invalid " + name); + } + } + + private static boolean isValidIpv4(String value) + { + if (!isAsciiIpv4Candidate(value)) + return false; + + String[] parts = value.split("\\.", -1); + + if (parts.length != 4) + return false; + + for (String part : parts) + { + if (part.isEmpty()) + return false; + + if (part.length() > 1 && part.charAt(0) == '0') + return false; + + int number = 0; + + for (int i = 0; i < part.length(); i++) + { + number = number * 10 + (part.charAt(i) - '0'); + + if (number > 255) + return false; + } + } + + return true; + } + + private static boolean isValidIpv6(String value) + { + if (value.isEmpty()) + return false; + + /* + * Explicitly reject IPv6 scope identifiers. HostSpec currently represents an IP address, not an + * interface-scoped address. + */ + if (value.indexOf('%') >= 0) + return false; + + try + { + return Inet6Address.ofLiteral(value) instanceof Inet6Address; + } + catch (IllegalArgumentException e) + { + return false; + } + } + + /** + * Validates the domain using Java's IDN implementation while preserving the original Unicode representation. + * + * The returned ASCII/Punycode value is deliberately discarded. + * + * USE_STD3_ASCII_RULES additionally applies the RFC 1122/1123 restrictions to ASCII labels. + */ + private static boolean isValidDomain(String value) + { + if (value.isEmpty()) + return false; + + /* + * A trailing root dot is valid. Java's IDN implementation handles the Unicode DNS separator characters + * according to its own IDN processing rules. + */ + try + { + String ascii = IDN.toASCII(value, IDN.USE_STD3_ASCII_RULES); + + if (ascii.isEmpty()) + return false; + + /* + * IDN.toASCII() performs IDNA processing, but keep these explicit DNS length checks here so the parser's + * accepted representation has a clear DNS-size boundary. + */ + if (ascii.length() > 255) + return false; + + String withoutRootDot = ascii.endsWith(".") ? ascii.substring(0, ascii.length() - 1) : ascii; + + if (withoutRootDot.isEmpty()) + return false; + + String[] labels = withoutRootDot.split("\\.", -1); + + for (String label : labels) + { + if (label.isEmpty() || label.length() > 63) + return false; + } + + return true; + } + catch (IllegalArgumentException e) + { + return false; + } + } + + private static IllegalArgumentException invalid(String input, String reason) + { + return new IllegalArgumentException(reason + ": \"" + input + "\""); + } + + public static record HostSpec(Kind kind, String host, Integer prefixLength, Integer port) + { + public HostSpec + { + Objects.requireNonNull(kind, "kind"); + Objects.requireNonNull(host, "host"); + + if (kind == Kind.IPV4 && prefixLength != MAX_IPV4_PREFIX) + throw new IllegalArgumentException("Invalid prefix length: " + prefixLength); + else if (kind == Kind.IPV6 && prefixLength != MAX_IPV6_PREFIX) + throw new IllegalArgumentException("Invalid prefix length: " + prefixLength); + else if (prefixLength != null) + { + int max = switch (kind) + { + case IPV4, IPV4_CIDR -> MAX_IPV4_PREFIX; + case IPV6, IPV6_CIDR -> MAX_IPV6_PREFIX; + default -> throw new IllegalArgumentException("Prefix length is only valid for IP kinds"); + }; + + if (prefixLength < 0 || prefixLength > max) + throw new IllegalArgumentException("Invalid prefix length: " + prefixLength); + } + + if (port != null && (port < 0 || port > MAX_PORT)) + throw new IllegalArgumentException("Invalid port: " + port); + } + + public boolean hasPort() + { + return port != null; + } + + public boolean isIp() + { + return kind == Kind.IPV4 || kind == Kind.IPV6 || kind == Kind.IPV4_CIDR || kind == Kind.IPV6_CIDR; + } + + public boolean isDomain() + { + return kind == Kind.DOMAIN; + } + + public boolean isDomainOrWildcard() + { + return isDomain() || kind == Kind.DOMAIN_WILDCARD_SINGLE_LEVEL || kind == Kind.DOMAIN_WILDCARD_MULTI_LEVEL; + } + + public String asciiHost() + { + return isDomainOrWildcard() ? IDN.toASCII(host, IDN.USE_STD3_ASCII_RULES) : host; + } + + @Override + public final String toString() + { + return switch (kind) + { + case Kind.DOMAIN, Kind.IPV4 -> host + (hasPort() ? ":" + port : ""); + case Kind.IPV6 -> "[" + host + "]" + (hasPort() ? ":" + port : ""); + + case Kind.DOMAIN_WILDCARD_SINGLE_LEVEL -> "*." + host + (hasPort() ? ":" + port : ""); + case Kind.DOMAIN_WILDCARD_MULTI_LEVEL -> "**." + host + (hasPort() ? ":" + port : ""); + + case Kind.IPV4_CIDR -> host + "/" + prefixLength + (hasPort() ? ":" + port : ""); + case Kind.IPV6_CIDR -> "[" + host + "/" + prefixLength + "]" + (hasPort() ? ":" + port : ""); + + default -> throw new IllegalArgumentException("Kind not supported: " + kind); + }; + } + } + + public static enum Kind + { + DOMAIN, + + DOMAIN_WILDCARD_SINGLE_LEVEL, DOMAIN_WILDCARD_MULTI_LEVEL, + + IPV4, IPV4_CIDR, + + IPV6, IPV6_CIDR + } + + private static record HostAndPort(String host, Integer port) + { + } +} \ No newline at end of file diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcher.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcher.java new file mode 100644 index 000000000..2c1623c4f --- /dev/null +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcher.java @@ -0,0 +1,38 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.common.config.network; + +import java.net.InetSocketAddress; + +public interface InetSocketAddressMatcher +{ + /** + * Matches all of the incoming addresses + */ + InetSocketAddressMatcher ALL = _ -> true; + + /** + * Matches none of the incoming addresses + */ + InetSocketAddressMatcher NONE = _ -> false; + + /** + * @param address + * not null + * @return true if given address matches one of the configured networks + */ + boolean matches(InetSocketAddress address); +} \ No newline at end of file diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcherList.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcherList.java new file mode 100644 index 000000000..804758caf --- /dev/null +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcherList.java @@ -0,0 +1,79 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.common.config.network; + +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Matches addresses against network definitions. + */ +public class InetSocketAddressMatcherList implements InetSocketAddressMatcher +{ + private final List networks; + + /** + * @param networks, + * null values are ignored + */ + public InetSocketAddressMatcherList(InetSocketAddressMatcher... networks) + { + this(Stream.of(networks)); + } + + /** + * @param networks + * may be null, null values are filtered + */ + public InetSocketAddressMatcherList(Stream networks) + { + this(networks == null ? null : networks.filter(Objects::nonNull).toList()); + } + + /** + * @param networks + * may be null, null values are filtered + */ + // null value filter in #matches(InetSocketAddress) + public InetSocketAddressMatcherList(Collection networks) + { + this.networks = networks == null ? List.of() : List.copyOf(networks); + } + + @Override + public boolean matches(InetSocketAddress address) + { + if (address == null) + return false; + + return networks.stream().filter(Objects::nonNull).anyMatch(n -> n.matches(address)); + } + + public List getNetworks() + { + return networks; + } + + @Override + public String toString() + { + return networks.stream().map(InetSocketAddressMatcher::toString).collect(Collectors.joining(", ", "[", "]")); + } +} diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticCidrMatcher.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticCidrMatcher.java new file mode 100644 index 000000000..3cf6d2c2b --- /dev/null +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticCidrMatcher.java @@ -0,0 +1,125 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.common.config.network; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import dev.dsf.common.config.network.HostSpecParser.HostSpec; + +public class StaticCidrMatcher implements InetSocketAddressMatcher +{ + public static InetSocketAddressMatcherList of(HostSpec... hostSpecs) + { + return of(hostSpecs == null ? List.of() : List.of(hostSpecs)); + } + + public static InetSocketAddressMatcherList of(Collection hostSpecs) + { + return new InetSocketAddressMatcherList(hostSpecs == null ? List.of() + : hostSpecs.stream().filter(HostSpec::isIp).map(StaticCidrMatcher::of).toList()); + } + + public static StaticCidrMatcher of(HostSpec hostSpec) + { + Objects.requireNonNull(hostSpec, "hostSpec"); + + return switch (hostSpec.kind()) + { + case IPV4 -> { + byte[] address = Inet4Address.ofLiteral(hostSpec.host()).getAddress(); + yield new StaticCidrMatcher(address, address.length * 8, hostSpec.port()); + } + case IPV6 -> { + byte[] address = Inet6Address.ofLiteral(hostSpec.host()).getAddress(); + yield new StaticCidrMatcher(address, address.length * 8, hostSpec.port()); + } + case IPV4_CIDR, IPV6_CIDR -> new StaticCidrMatcher(InetAddress.ofLiteral(hostSpec.host()).getAddress(), + hostSpec.prefixLength(), hostSpec.port()); + + default -> throw new IllegalArgumentException("hostSpec.kind '" + hostSpec.kind() + "' not supported"); + }; + } + + private final byte[] network; + private final int prefixLength; + private final Integer port; + + private StaticCidrMatcher(byte[] network, int prefixLength, Integer port) + { + this.network = network; + this.prefixLength = prefixLength; + this.port = port; + } + + @Override + public boolean matches(InetSocketAddress address) + { + if (address == null) + return false; + + if (address.isUnresolved()) + return false; + + if (!portMatches(port, address.getPort())) + return false; + + byte[] candidate = address.getAddress().getAddress(); + + // IPv4 and IPv6 addresses have different lengths and can never match each other + if (candidate.length != network.length) + return false; + + int fullBytes = prefixLength / 8; + int remainingBits = prefixLength % 8; + + for (int i = 0; i < fullBytes; i++) + if (candidate[i] != network[i]) + return false; + + if (remainingBits == 0) + return true; + + int mask = 0xFF << (8 - remainingBits); + + return (candidate[fullBytes] & mask) == (network[fullBytes] & mask); + } + + private boolean portMatches(Integer expectedPort, int candidatePort) + { + return expectedPort == null || expectedPort == candidatePort; + } + + @Override + public String toString() + { + try + { + return InetAddress.getByAddress(network).getHostAddress() + "/" + prefixLength + + (port == null ? "" : ":" + port); + } + catch (UnknownHostException e) + { + throw new IllegalStateException("Invalid CIDR network address", e); + } + } +} \ No newline at end of file diff --git a/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/DynamicHostnameMatcherTest.java b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/DynamicHostnameMatcherTest.java new file mode 100644 index 000000000..8fb50b423 --- /dev/null +++ b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/DynamicHostnameMatcherTest.java @@ -0,0 +1,185 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.common.config.network; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneOffset; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; + +public class DynamicHostnameMatcherTest +{ + private static final Duration REFRESH_TIMEOUT = Duration.ofMinutes(5); + + @Test + public void matchesResolvedAddress() throws Exception + { + DynamicHostnameMatcher matcher = matcher(clockAt("2026-01-01T00:00:00Z"), + _ -> new InetAddress[] { InetAddress.ofLiteral("192.0.2.10") }); + + assertTrue(matcher.matches(new InetSocketAddress("192.0.2.10", 0))); + } + + @Test + public void doesNotMatchDifferentAddress() throws Exception + { + DynamicHostnameMatcher matcher = matcher(clockAt("2026-01-01T00:00:00Z"), + _ -> new InetAddress[] { InetAddress.ofLiteral("192.0.2.10") }); + + assertFalse(matcher.matches(new InetSocketAddress("192.0.2.11", 0))); + } + + @Test + public void doesNotMatchUnresolvedAddress() + { + DynamicHostnameMatcher matcher = matcher(clockAt("2026-01-01T00:00:00Z"), + _ -> new InetAddress[] { InetAddress.getLoopbackAddress() }); + + assertFalse(matcher.matches(InetSocketAddress.createUnresolved("example.com", 0))); + } + + @Test + public void refreshesAfterTimeout() throws Exception + { + Instant initialTime = Instant.parse("2026-01-01T00:00:00Z"); + AtomicReference currentTime = new AtomicReference<>(initialTime); + + InetAddress firstAddress = InetAddress.ofLiteral("192.0.2.10"); + AtomicReference resolved = new AtomicReference<>(new InetAddress[] { firstAddress }); + + DynamicHostnameMatcher matcher = matcher(new MutableClock(currentTime), _ -> resolved.get()); + + assertTrue(matcher.matches(new InetSocketAddress("192.0.2.10", 443))); + + // Change DNS result, but cache has not expired + InetAddress secondAddress = InetAddress.ofLiteral("192.0.2.20"); + resolved.set(new InetAddress[] { secondAddress }); + currentTime.set(initialTime.plus(Duration.ofMinutes(4))); + + assertTrue(matcher.matches(new InetSocketAddress("192.0.2.10", 443))); + + // Cache expired, so DNS is resolved again + currentTime.set(initialTime.plus(Duration.ofMinutes(6))); + + assertFalse(matcher.matches(new InetSocketAddress("192.0.2.10", 443))); + assertTrue(matcher.matches(new InetSocketAddress("192.0.2.20", 443))); + } + + @Test + public void failsClosedWhenRefreshFails() throws Exception + { + Instant initialTime = Instant.parse("2026-01-01T00:00:00Z"); + AtomicReference currentTime = new AtomicReference<>(initialTime); + + InetAddress allowed = InetAddress.ofLiteral("192.0.2.10"); + + AtomicReference fail = new AtomicReference<>(false); + DynamicHostnameMatcher matcher = matcher(new MutableClock(currentTime), _ -> + { + if (fail.get()) + throw new UnknownHostException("DNS temporarily unavailable"); + + return new InetAddress[] { allowed }; + }); + + // Initially the address is allowed + assertTrue(matcher.matches(new InetSocketAddress("192.0.2.10", 443))); + + // Cause the next DNS refresh to fail + fail.set(true); + + currentTime.set(initialTime.plus(Duration.ofMinutes(6))); + + // The old address must no longer be authorized + assertFalse(matcher.matches(new InetSocketAddress("192.0.2.10", 443))); + } + + @Test + public void recoversAfterFailedRefresh() throws Exception + { + Instant initialTime = Instant.parse("2026-01-01T00:00:00Z"); + AtomicReference currentTime = new AtomicReference<>(initialTime); + + InetAddress allowed = InetAddress.ofLiteral("192.0.2.10"); + + AtomicReference fail = new AtomicReference<>(true); + DynamicHostnameMatcher matcher = matcher(new MutableClock(currentTime), _ -> + { + if (fail.get()) + { + throw new UnknownHostException("DNS failure"); + } + return new InetAddress[] { allowed }; + }); + + // Constructor refresh failed, so access is denied + assertFalse(matcher.matches(new InetSocketAddress("192.0.2.10", 443))); + + // Allow DNS to recover + fail.set(false); + + currentTime.set(initialTime.plus(Duration.ofMinutes(6))); + + assertTrue(matcher.matches(new InetSocketAddress("192.0.2.10", 443))); + } + + private static DynamicHostnameMatcher matcher(Clock clock, DynamicHostnameMatcher.DnsResolver resolver) + { + return new DynamicHostnameMatcher(REFRESH_TIMEOUT, "example.com", clock, resolver); + } + + private static Clock clockAt(String instant) + { + return Clock.fixed(Instant.parse(instant), ZoneOffset.UTC); + } + + private static final class MutableClock extends Clock + { + private final AtomicReference currentTime; + + private MutableClock(AtomicReference currentTime) + { + this.currentTime = currentTime; + } + + @Override + public ZoneOffset getZone() + { + return ZoneOffset.UTC; + } + + @Override + public Clock withZone(java.time.ZoneId zone) + { + return this; + } + + @Override + public Instant instant() + { + return currentTime.get(); + } + } +} diff --git a/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/HostSpecParserTest.java b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/HostSpecParserTest.java new file mode 100644 index 000000000..fc893335b --- /dev/null +++ b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/HostSpecParserTest.java @@ -0,0 +1,308 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.common.config.network; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import dev.dsf.common.config.network.HostSpecParser.HostSpec; +import dev.dsf.common.config.network.HostSpecParser.Kind; + +@RunWith(Parameterized.class) +public class HostSpecParserTest +{ + private final String input; + private final boolean valid; + + private final HostSpecParser.Kind expectedKind; + private final String expectedHost; + private final Integer expectedPrefixLength; + private final Integer expectedPort; + + public HostSpecParserTest(String input, boolean valid, HostSpecParser.Kind expectedKind, String expectedHost, + Integer expectedPrefixLength, Integer expectedPort) + { + this.input = input; + this.valid = valid; + this.expectedKind = expectedKind; + this.expectedHost = expectedHost; + this.expectedPrefixLength = expectedPrefixLength; + this.expectedPort = expectedPort; + } + + @Parameterized.Parameters(name = "{index}: {0}") + public static Collection parameters() + { + return Arrays.asList(new Object[][] { + + // VALID - Domains + valid("example.com", HostSpecParser.Kind.DOMAIN, "example.com", null, null), + valid("example.com:80", HostSpecParser.Kind.DOMAIN, "example.com", null, 80), + valid("EXAMPLE.COM", HostSpecParser.Kind.DOMAIN, "EXAMPLE.COM", null, null), + valid("example.com.", HostSpecParser.Kind.DOMAIN, "example.com.", null, null), + valid("sub.example.com:443", HostSpecParser.Kind.DOMAIN, "sub.example.com", null, 443), + valid("a-b.example.com", HostSpecParser.Kind.DOMAIN, "a-b.example.com", null, null), + valid("a1.example2.com", HostSpecParser.Kind.DOMAIN, "a1.example2.com", null, null), + valid("localhost", HostSpecParser.Kind.DOMAIN, "localhost", null, null), + valid(" example.com ", HostSpecParser.Kind.DOMAIN, "example.com", null, null), + + // VALID - Unicode / IDN + valid("münchen.de", HostSpecParser.Kind.DOMAIN, "münchen.de", null, null), + valid("münchen.de:443", HostSpecParser.Kind.DOMAIN, "münchen.de", null, 443), + valid("例え.テスト", HostSpecParser.Kind.DOMAIN, "例え.テスト", null, null), + valid("例え.テスト:443", HostSpecParser.Kind.DOMAIN, "例え.テスト", null, 443), + valid("παράδειγμα.δοκιμή", HostSpecParser.Kind.DOMAIN, "παράδειγμα.δοκιμή", null, null), + valid("مثال.إختبار", HostSpecParser.Kind.DOMAIN, "مثال.إختبار", null, null), + valid("한국.한국", HostSpecParser.Kind.DOMAIN, "한국.한국", null, null), + valid("café.example", HostSpecParser.Kind.DOMAIN, "café.example", null, null), + valid("école.fr", HostSpecParser.Kind.DOMAIN, "école.fr", null, null), + valid("例123.テスト", HostSpecParser.Kind.DOMAIN, "例123.テスト", null, null), + + // VALID - One-level wildcards + valid("*.example.com", HostSpecParser.Kind.DOMAIN_WILDCARD_SINGLE_LEVEL, "example.com", null, null), + valid("*.example.com:443", HostSpecParser.Kind.DOMAIN_WILDCARD_SINGLE_LEVEL, "example.com", null, 443), + valid("*.sub.example.com", HostSpecParser.Kind.DOMAIN_WILDCARD_SINGLE_LEVEL, "sub.example.com", null, + null), + valid("*.example.com.", HostSpecParser.Kind.DOMAIN_WILDCARD_SINGLE_LEVEL, "example.com.", null, null), + valid("*.münchen.de", HostSpecParser.Kind.DOMAIN_WILDCARD_SINGLE_LEVEL, "münchen.de", null, null), + valid("*.例え.テスト:443", HostSpecParser.Kind.DOMAIN_WILDCARD_SINGLE_LEVEL, "例え.テスト", null, 443), + + // VALID - Multi-level wildcards + valid("**.example.com", HostSpecParser.Kind.DOMAIN_WILDCARD_MULTI_LEVEL, "example.com", null, null), + valid("**.example.com:443", HostSpecParser.Kind.DOMAIN_WILDCARD_MULTI_LEVEL, "example.com", null, 443), + valid("**.sub.example.com", HostSpecParser.Kind.DOMAIN_WILDCARD_MULTI_LEVEL, "sub.example.com", null, + null), + valid("**.münchen.de", HostSpecParser.Kind.DOMAIN_WILDCARD_MULTI_LEVEL, "münchen.de", null, null), + valid("**.例え.テスト:443", HostSpecParser.Kind.DOMAIN_WILDCARD_MULTI_LEVEL, "例え.テスト", null, 443), + + // VALID - IPv4 + valid("0.0.0.0", HostSpecParser.Kind.IPV4, "0.0.0.0", 32, null), + valid("127.0.0.1", HostSpecParser.Kind.IPV4, "127.0.0.1", 32, null), + valid("192.168.1.1", HostSpecParser.Kind.IPV4, "192.168.1.1", 32, null), + valid("255.255.255.255", HostSpecParser.Kind.IPV4, "255.255.255.255", 32, null), + valid("192.168.1.1:80", HostSpecParser.Kind.IPV4, "192.168.1.1", 32, 80), + valid("10.0.0.1:0", HostSpecParser.Kind.IPV4, "10.0.0.1", 32, 0), + valid("10.0.0.1:65535", HostSpecParser.Kind.IPV4, "10.0.0.1", 32, 65535), + + // VALID - IPv4 CIDR + valid("0.0.0.0/0", HostSpecParser.Kind.IPV4_CIDR, "0.0.0.0", 0, null), + valid("192.168.1.0/24", HostSpecParser.Kind.IPV4_CIDR, "192.168.1.0", 24, null), + valid("192.168.1.0/24:80", HostSpecParser.Kind.IPV4_CIDR, "192.168.1.0", 24, 80), + valid("255.255.255.255/32", HostSpecParser.Kind.IPV4_CIDR, "255.255.255.255", 32, null), + + // VALID - IPv6 - Brackets are mandatory + valid("[::1]", HostSpecParser.Kind.IPV6, "::1", 128, null), + valid("[::]", HostSpecParser.Kind.IPV6, "::", 128, null), + valid("[2001:db8::1]", HostSpecParser.Kind.IPV6, "2001:db8::1", 128, null), + valid("[2001:0db8:0000:0000:0000:0000:0000:0001]", HostSpecParser.Kind.IPV6, + "2001:0db8:0000:0000:0000:0000:0000:0001", 128, null), + valid("[FE80::1]", HostSpecParser.Kind.IPV6, "FE80::1", 128, null), + valid("[::1]:80", HostSpecParser.Kind.IPV6, "::1", 128, 80), + valid("[2001:db8::1]:443", HostSpecParser.Kind.IPV6, "2001:db8::1", 128, 443), + valid("[::1]:0", HostSpecParser.Kind.IPV6, "::1", 128, 0), + valid("[::1]:65535", HostSpecParser.Kind.IPV6, "::1", 128, 65535), + + // VALID - IPv6 CIDR - Brackets are mandatory + valid("[::/0]", HostSpecParser.Kind.IPV6_CIDR, "::", 0, null), + valid("[2001:db8::/32]", HostSpecParser.Kind.IPV6_CIDR, "2001:db8::", 32, null), + valid("[2001:db8::/32]:443", HostSpecParser.Kind.IPV6_CIDR, "2001:db8::", 32, 443), + valid("[::1/128]", HostSpecParser.Kind.IPV6_CIDR, "::1", 128, null), + valid("[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128]:65535", HostSpecParser.Kind.IPV6_CIDR, + "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128, 65535), + + // INVALID - Empty + invalid(""), invalid(" "), invalid(" "), invalid("\t"), invalid("\n"), + + // INVALID - Domains + invalid(".example.com"), invalid("example..com"), invalid("example.com.."), invalid("-example.com"), + invalid("example-.com"), invalid("example.-com"), invalid("example.com-"), invalid("example_foo.com"), + invalid("example!com"), invalid("example@com"), invalid("example/com"), invalid("foo..bar"), + + // INVALID - Unicode domains - Emoji is not a valid IDN letter/digit + invalid("example.😀"), + + // INVALID - Unicode domains - Symbols are not valid domain-label characters + invalid("例え.テスト!"), invalid("例え..テスト"), + + // INVALID - Unicode domains - Hyphen restrictions apply to Unicode labels + invalid("-例え.テスト"), invalid("例え-.テスト"), invalid("例え.-テスト"), invalid("例え.テスト-"), + + // INVALID - Domain ports + invalid("example.com:"), invalid("example.com:abc"), invalid("example.com:-1"), + invalid("example.com:+1"), invalid("example.com:65536"), invalid("example.com:999999999999999999999"), + invalid("münchen.de:"), invalid("例え.テスト:abc"), invalid("例え.テスト:65536"), + + // INVALID - Domain ports - Unicode/full-width digits are not valid port syntax + invalid("example.com:80"), invalid("example.com:٨٠"), + + // INVALID - Wildcards + invalid("*"), invalid("*."), invalid("**."), invalid("*example.com"), invalid("**example.com"), + invalid("***.example.com"), invalid("*.*.example.com"), invalid("**.*.example.com"), + invalid("*.example.com:"), invalid("*.example.com:abc"), invalid("*.example.com:65536"), + invalid("*.münchen.de:"), invalid("*.例え.テスト:abc"), + + // INVALID - IPv4 + invalid("1.2.3.256"), invalid("256.2.3.4"), invalid("1.256.3.4"), invalid("1.2.256.4"), + invalid("01.2.3.4"), invalid("1.02.3.4"), invalid("1.2.03.4"), invalid("1.2.3.04"), invalid("1.2.3."), + invalid(".1.2.3"), invalid("1..2.3"), invalid("1.2..3"), + + // VALID - Domain - that look like IPv4 + valid("1.2.3", Kind.DOMAIN, "1.2.3", null, null), + valid("1.2.3.4.5", Kind.DOMAIN, "1.2.3.4.5", null, null), + valid("1.2.3.a", Kind.DOMAIN, "1.2.3.a", null, null), + valid("a.2.3.4", Kind.DOMAIN, "a.2.3.4", null, null), + valid("1.2.3.4.", Kind.DOMAIN, "1.2.3.4.", null, null), + valid("127.0.0.1", Kind.DOMAIN, "127.0.0.1", null, null), + valid("127.0.0.1", Kind.DOMAIN, "127.0.0.1", null, null), + + // INVALID - IPv4 CIDR + invalid("1.2.3.4/"), invalid("1.2.3.4/33"), invalid("1.2.3.4/-1"), invalid("1.2.3.4/+1"), + invalid("1.2.3.4/abc"), invalid("1.2.3.4/24/25"), + + // INVALID - IPv4 CIDR - Unicode digits are not valid prefix syntax + invalid("1.2.3.4/24"), invalid("1.2.3.4/٢٤"), + + // INVALID - IPv4 ports + invalid("1.2.3.4:"), invalid("1.2.3.4:abc"), invalid("1.2.3.4:-1"), invalid("1.2.3.4:+80"), + invalid("1.2.3.4:65536"), + + // INVALID - IPv6 must be bracketed + invalid("::1"), invalid("::"), invalid("2001:db8::1"), invalid("2001:db8::/32"), invalid("::1:80"), + invalid("2001:db8::1:443"), invalid("2001:db8::/64:443"), + + // INVALID - Bracketed IPv6 + invalid("["), invalid("[]"), invalid("[::1"), invalid("[::1]]"), invalid("[::1]foo"), + invalid("[::1]foo:80"), invalid("[::1]:"), invalid("[::1]:abc"), invalid("[::1]:-1"), + invalid("[::1]:+80"), invalid("[::1]:65536"), + + // INVALID - IPv6 CIDR + invalid("[2001:db8::/]"), invalid("[2001:db8::/129]"), invalid("[2001:db8::/-1]"), + invalid("[2001:db8::/+1]"), invalid("[2001:db8::/abc]"), invalid("[2001:db8::/32/64]"), + invalid("[::1/129]"), + + // Unicode digits are not valid IPv6 prefix syntax + invalid("[::1/128]"), invalid("[::1/١٢٨]"), + + // INVALID - IPv6 syntax + invalid("[1:2:3:4:5:6:7:8:9]"), invalid("[1:2:3:4:5:6:7]"), invalid("[gggg::1]"), invalid("[1:::1]"), + invalid("[:::]"), invalid("[2001:db8:::1]"), invalid("[::1%eth0]"), + + // INVALID - Ambiguous host/port syntax + invalid("example.com:80:90"), invalid("münchen.de:80:90"), invalid("例え.テスト:80:90"), + invalid("1.2.3.4:80:90"), invalid("example.com::80"), invalid("1.2.3.4::80"), + + // IDNA test cases + valid("bücher.de", HostSpecParser.Kind.DOMAIN, "bücher.de", null, null), + valid("BÜCHER.DE", HostSpecParser.Kind.DOMAIN, "BÜCHER.DE", null, null), + valid("e\u0301xample.com", HostSpecParser.Kind.DOMAIN, "e\u0301xample.com", null, null), + valid("日本語.jp", HostSpecParser.Kind.DOMAIN, "日本語.jp", null, null), + valid("مثال.إختبار", HostSpecParser.Kind.DOMAIN, "مثال.إختبار", null, null), + valid("example。com", HostSpecParser.Kind.DOMAIN, "example。com", null, null), + + invalid("example.😀") }); + } + + private static Object[] valid(String input, HostSpecParser.Kind kind, String host, Integer prefixLength, + Integer port) + { + return new Object[] { input, true, kind, host, prefixLength, port }; + } + + private static Object[] invalid(String input) + { + return new Object[] { input, false, null, null, null, null }; + } + + @Test + public void parse() + { + if (!valid) + { + assertInvalid(); + return; + } + + HostSpec actual = HostSpecParser.parse(input); + + assertEquals("kind", expectedKind, actual.kind()); + assertEquals("host", expectedHost, actual.host()); + assertEquals("prefixLength", expectedPrefixLength, actual.prefixLength()); + assertEquals("port", expectedPort, actual.port()); + + assertEquals("isIp", expectedPrefixLength != null, actual.isIp()); + assertEquals("isDomain", expectedPrefixLength == null, actual.isDomainOrWildcard()); + + assertEquals("hasPort", expectedPort != null, actual.hasPort()); + + assertEquals("toString", input == null ? null : input.trim(), actual.toString()); + } + + private void assertInvalid() + { + try + { + HostSpecParser.parse(input); + fail("Expected IllegalArgumentException for: " + input); + } + catch (IllegalArgumentException expected) + { + // Expected + } + } + + @Test + public void parseAllPreservesOrderAndUnicode() + { + List inputs = List.of("example.com", "münchen.de", "例え.テスト", "192.168.1.1", "[::1]:443", + "*.παράδειγμα.δοκιμή"); + + List result = HostSpecParser.parse(inputs); + + assertEquals(6, result.size()); + assertEquals(HostSpecParser.Kind.DOMAIN, result.get(0).kind()); + assertEquals("münchen.de", result.get(1).host()); + assertEquals("例え.テスト", result.get(2).host()); + assertEquals(HostSpecParser.Kind.IPV4, result.get(3).kind()); + assertEquals(HostSpecParser.Kind.IPV6, result.get(4).kind()); + assertEquals(Integer.valueOf(443), result.get(4).port()); + assertEquals(HostSpecParser.Kind.DOMAIN_WILDCARD_SINGLE_LEVEL, result.get(5).kind()); + assertEquals("παράδειγμα.δοκιμή", result.get(5).host()); + } + + @Test + public void parseAllFiltersNullElement() + { + List list = HostSpecParser.parse(Arrays.asList("example.com", null, "192.168.1.1")); + assertNotNull(list); + assertEquals(2, list.size()); + } + + @Test(expected = IllegalArgumentException.class) + public void parseRejectsNulCharacter() + { + String input = "foo" + '\0' + "bar.example"; + HostSpecParser.parse(input); + } +} \ No newline at end of file diff --git a/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/InetSocketAddressMatcherListTest.java b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/InetSocketAddressMatcherListTest.java new file mode 100644 index 000000000..d87ec43d9 --- /dev/null +++ b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/InetSocketAddressMatcherListTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.common.config.network; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +import org.junit.Test; + +public class InetSocketAddressMatcherListTest +{ + @Test + public void constructorAcceptsNull() throws Exception + { + new InetSocketAddressMatcherList((Stream) null); + new InetSocketAddressMatcherList((Collection) null); + } + + @Test + public void getNetworksReturnsUnmodifiableList() throws Exception + { + assertThrows(UnsupportedOperationException.class, + () -> new InetSocketAddressMatcherList((Stream) null).getNetworks() + .add(InetSocketAddressMatcher.ALL)); + + assertThrows(UnsupportedOperationException.class, + () -> new InetSocketAddressMatcherList((Collection) null).getNetworks() + .add(InetSocketAddressMatcher.ALL)); + + assertThrows(UnsupportedOperationException.class, + () -> new InetSocketAddressMatcherList(Stream.of(InetSocketAddressMatcher.ALL)).getNetworks() + .add(InetSocketAddressMatcher.ALL)); + + assertThrows(UnsupportedOperationException.class, + () -> new InetSocketAddressMatcherList(List.of(InetSocketAddressMatcher.ALL)).getNetworks() + .add(InetSocketAddressMatcher.ALL)); + } + + @Test + public void nullAddressDoesNotMatch() throws Exception + { + assertFalse(new InetSocketAddressMatcherList().matches(null)); + assertFalse(new InetSocketAddressMatcherList(InetSocketAddressMatcher.ALL).matches(null)); + } + + @Test + public void matchesMatchesAnyConfiguredRule() throws Exception + { + assertTrue(new InetSocketAddressMatcherList(InetSocketAddressMatcher.NONE, InetSocketAddressMatcher.ALL) + .matches(InetSocketAddress.createUnresolved("localhost", 0))); + assertTrue(new InetSocketAddressMatcherList(InetSocketAddressMatcher.ALL, InetSocketAddressMatcher.NONE) + .matches(InetSocketAddress.createUnresolved("localhost", 0))); + } +} diff --git a/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/StaticCidrMatcherTest.java b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/StaticCidrMatcherTest.java new file mode 100644 index 000000000..1048efa9e --- /dev/null +++ b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/StaticCidrMatcherTest.java @@ -0,0 +1,256 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.common.config.network; + +import static dev.dsf.common.config.network.HostSpecParser.parse; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.net.InetAddress; +import java.net.InetSocketAddress; + +import org.junit.Test; + +public class StaticCidrMatcherTest +{ + private InetSocketAddress ofLiteral(String literal) + { + return ofLiteral(literal, 0); + } + + private InetSocketAddress ofLiteral(String literal, int port) + { + return new InetSocketAddress(InetAddress.ofLiteral(literal), port); + } + + @Test + public void shouldMatchIpv4AddressWithinCidr() throws Exception + { + StaticCidrMatcher matcher = StaticCidrMatcher.of(parse("10.0.0.0/24")); + + assertTrue(matcher.matches(ofLiteral("10.0.0.1"))); + assertTrue(matcher.matches(ofLiteral("10.0.0.254"))); + + assertFalse(matcher.matches(ofLiteral("10.0.1.1"))); + assertFalse(matcher.matches(ofLiteral("192.168.1.1"))); + } + + @Test + public void shouldMatchIpv4AddressWithinCidrAndPort() throws Exception + { + StaticCidrMatcher matcher = StaticCidrMatcher.of(parse("10.0.0.0/24:80")); + + assertTrue(matcher.matches(ofLiteral("10.0.0.1", 80))); + assertTrue(matcher.matches(ofLiteral("10.0.0.254", 80))); + + assertFalse(matcher.matches(ofLiteral("10.0.0.1"))); + assertFalse(matcher.matches(ofLiteral("10.0.0.254"))); + + assertFalse(matcher.matches(ofLiteral("10.0.0.1", 443))); + assertFalse(matcher.matches(ofLiteral("10.0.0.254", 443))); + + assertFalse(matcher.matches(ofLiteral("10.0.1.1"))); + assertFalse(matcher.matches(ofLiteral("192.168.1.1"))); + } + + @Test + public void shouldMatchIpv4ExactAddressUsing32() throws Exception + { + StaticCidrMatcher matcher = StaticCidrMatcher.of(parse("10.0.0.42/32")); + + assertTrue(matcher.matches(ofLiteral("10.0.0.42"))); + + assertFalse(matcher.matches(ofLiteral("10.0.0.41"))); + assertFalse(matcher.matches(ofLiteral("10.0.0.43"))); + } + + @Test + public void shouldMatchIpv4ExactAddress() throws Exception + { + StaticCidrMatcher matcher = StaticCidrMatcher.of(parse("10.0.0.42")); + + assertTrue(matcher.matches(ofLiteral("10.0.0.42"))); + + assertFalse(matcher.matches(ofLiteral("10.0.0.41"))); + assertFalse(matcher.matches(ofLiteral("10.0.0.43"))); + } + + @Test + public void shouldMatchIpv6AddressWithinCidr() throws Exception + { + StaticCidrMatcher matcher = StaticCidrMatcher.of(parse("[2001:db8:1234::/64]")); + + assertTrue(matcher.matches(ofLiteral("2001:db8:1234::1"))); + assertTrue(matcher.matches(ofLiteral("2001:db8:1234:0:abcd::1"))); + + assertFalse(matcher.matches(ofLiteral("2001:db8:1235::1"))); + } + + @Test + public void shouldMatchIpv6AddressWithinCidrAndPort() throws Exception + { + StaticCidrMatcher matcher = StaticCidrMatcher.of(parse("[2001:db8:1234::/64]:80")); + + assertTrue(matcher.matches(ofLiteral("2001:db8:1234::1", 80))); + assertTrue(matcher.matches(ofLiteral("2001:db8:1234:0:abcd::1", 80))); + + assertFalse(matcher.matches(ofLiteral("2001:db8:1234::1"))); + assertFalse(matcher.matches(ofLiteral("2001:db8:1234:0:abcd::1"))); + + assertFalse(matcher.matches(ofLiteral("2001:db8:1234::1", 443))); + assertFalse(matcher.matches(ofLiteral("2001:db8:1234:0:abcd::1", 443))); + + assertFalse(matcher.matches(ofLiteral("2001:db8:1235::1"))); + } + + @Test + public void shouldMatchIpv6ExactAddressUsing128() throws Exception + { + StaticCidrMatcher matcher = StaticCidrMatcher.of(parse("[2001:db8::42/128]")); + + assertTrue(matcher.matches(ofLiteral("2001:db8::42"))); + + assertFalse(matcher.matches(ofLiteral("2001:db8::41"))); + assertFalse(matcher.matches(ofLiteral("2001:db8::43"))); + } + + @Test + public void shouldMatchIpv6ExactAddress() throws Exception + { + StaticCidrMatcher matcher = StaticCidrMatcher.of(parse("[2001:db8::42]")); + + assertTrue(matcher.matches(ofLiteral("2001:db8::42"))); + + assertFalse(matcher.matches(ofLiteral("2001:db8::41"))); + assertFalse(matcher.matches(ofLiteral("2001:db8::43"))); + } + + @Test + public void shouldNotMatchIpv4NetworkAgainstIpv6Address() throws Exception + { + StaticCidrMatcher matcher = StaticCidrMatcher.of(parse("10.0.0.0/8")); + + assertFalse(matcher.matches(ofLiteral("2001:db8::1"))); + } + + @Test + public void shouldNotMatchIpv6NetworkAgainstIpv4Address() throws Exception + { + StaticCidrMatcher matcher = StaticCidrMatcher.of(parse("[2001:db8::/32]")); + + assertFalse(matcher.matches(ofLiteral("10.0.0.1"))); + } + + @Test + public void shouldSupportMultipleTrustedNetworks() throws Exception + { + InetSocketAddressMatcher matcher = StaticCidrMatcher.of(parse("10.0.0.0/24"), parse("172.16.0.0/16"), + parse("[2001:db8::/64]")); + + assertTrue(matcher.matches(ofLiteral("10.0.0.42"))); + assertTrue(matcher.matches(ofLiteral("172.16.42.10"))); + assertTrue(matcher.matches(ofLiteral("2001:db8::1"))); + + assertFalse(matcher.matches(ofLiteral("192.168.1.1"))); + assertFalse(matcher.matches(ofLiteral("2001:db9::1"))); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldRejectInvalidCidrPrefix() + { + StaticCidrMatcher.of(parse("10.0.0.0/33")); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldRejectInvalidIpv6CidrPrefix() + { + StaticCidrMatcher.of(parse("2001:db8::/129")); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldRejectMalformedCidr() + { + StaticCidrMatcher.of(parse("10.0.0.0/not-a-number")); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldRejectUnknownHostname() + { + StaticCidrMatcher.of(parse("this-host-definitely-does-not-exist.invalid")); + } + + @Test + public void nullAddressDoesNotMatch() + { + StaticCidrMatcher matcher = StaticCidrMatcher.of(parse("10.0.0.0/24")); + + assertFalse(matcher.matches(null)); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldNotResolveHostnameInsideCidr() + { + StaticCidrMatcher.of(parse("proxy.example.com/24")); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldRejectMalformedIpv4Literal() + { + StaticCidrMatcher.of(parse("10.0.0.999/32")); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldRejectMalformedIpv6Literal() + { + StaticCidrMatcher.of(parse("2001:db8")); + } + + @Test + public void matchesIpv4CidrWithPartialByte() throws Exception + { + StaticCidrMatcher matcher = StaticCidrMatcher.of(parse("192.168.1.128/25")); + + assertTrue(matcher.matches(ofLiteral("192.168.1.128"))); + assertTrue(matcher.matches(ofLiteral("192.168.1.200"))); + assertTrue(matcher.matches(ofLiteral("192.168.1.255"))); + + assertFalse(matcher.matches(ofLiteral("192.168.1.127"))); + assertFalse(matcher.matches(ofLiteral("192.168.1.1"))); + } + + @Test + public void matchesIpv6CidrWithPartialByte() throws Exception + { + StaticCidrMatcher matcher = StaticCidrMatcher.of(parse("[2001:db8::/57]")); + + // First address: 2001:0db8:0000:0000:0000:0000:0000:0000 + assertTrue(matcher.matches(ofLiteral("2001:db8::"))); + + // Last address: 2001:0db8:0000:007f:ffff:ffff:ffff:ffff + assertTrue(matcher.matches(ofLiteral("2001:db8:0:7f:ffff:ffff:ffff:ffff"))); + + // First address outside: 2001:0db8:0000:0080:0000:0000:0000:0000 + assertFalse(matcher.matches(ofLiteral("2001:db8:0:80::"))); + } + + @Test + public void shouldNotMatchUnresolvedAddress() throws Exception + { + InetSocketAddressMatcher matcher = StaticCidrMatcher.of(parse("10.0.0.0/24")); + + assertFalse(matcher.matches(InetSocketAddress.createUnresolved("unresolved.invalid", 666))); + } +} diff --git a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java index b7fd7f6ce..d90427ad2 100644 --- a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java +++ b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java @@ -82,6 +82,7 @@ import dev.dsf.common.auth.StatusPortAuthenticator; import dev.dsf.common.buildinfo.BuildInfoReader; import dev.dsf.common.buildinfo.BuildInfoReaderImpl; +import dev.dsf.common.config.network.HostSpecParser.HostSpec; import dev.dsf.common.docker.secrets.DockerSecretsPropertySourceFactory; import dev.dsf.common.documentation.Documentation; import dev.dsf.common.jetty.HttpClientWithGetRetry; @@ -96,7 +97,7 @@ @Configuration @PropertySource(value = "file:conf/jetty.properties", encoding = "UTF-8", ignoreResourceNotFound = true) -public abstract class AbstractJettyConfig extends AbstractCertificateConfig +public abstract class AbstractJettyConfig extends AbstractCertificateAndProxyConfig { private static final Logger logger = LoggerFactory.getLogger(AbstractJettyConfig.class); @@ -124,6 +125,16 @@ public abstract class AbstractJettyConfig extends AbstractCertificateConfig @Value("${dev.dsf.server.auth.client.certificate.header:X-ClientCert}") private String clientCertificateHeaderName; + @Documentation(description = "Defines allowed source IPs for the reverse proxy, supported definitions: by hostname - resolved periodically (see *DEV_DSF_SERVER_AUTH_TRUST_REVERSE_PROXY_HOSTNAME_REFRESH_TIMEOUT*), by single IPv4 or IPv6 address, by IPv4 CIDR or IPv6 CIDR network; comma or space separated list, YAML block scalars supported; use `" + + HOST_SPEC_LIST_DISABLED + + "` to allow all incoming IP addresses", example = "proxy, ingress.cluster.local, 192.168.1.1, 192.168.1.0/24, [2001:db8::1], [2001:db8::/32]") + @Value("#{'${dev.dsf.server.auth.trust.reverse.proxy:proxy}'.trim().split('[,\\s]+')}") + private List trustedReverseProxies; + + @Documentation(description = "Refresh timeout after which a trusted reverse proxy hostname is re-resolved", recommendation = "The refresh timeout should be chosen according to the acceptable stale-authorization window and e.g. the expected reverse-proxy replacement time") + @Value("${dev.dsf.server.auth.trust.reverse.proxy.hostname.refresh.timeout:PT10S}") + private String trustedReverseProxiesRefreshTimeout; + @Documentation(description = "Folder with PEM encoded files (*.crt, *.pem) or a single PEM encoded file with one or more trusted full CA chains to validate client certificates for https connections from local and remote clients", recommendation = "Add file to default folder via bind mount or use docker secret file to configure", example = "/run/secrets/app_client_trust_certificates.pem") @Value("${dev.dsf.server.auth.trust.client.certificate.cas:ca/client_ca_chains}") private String clientCertificateTrustStoreFileOrFolder; @@ -240,7 +251,12 @@ public abstract class AbstractJettyConfig extends AbstractCertificateConfig protected final Function httpApiConnector() { - return JettyServer.httpConnector(apiHost, apiPort, clientCertificateHeaderName); + Duration trustedReverseProxiesDnsRefreshTimeout = Duration.parse(trustedReverseProxiesRefreshTimeout); + List trustedReverseProxies = parseHostSpecList("dev.dsf.server.auth.trust.reverse.proxy", + this.trustedReverseProxies, true); + + return JettyServer.httpConnector(apiHost, apiPort, clientCertificateHeaderName, + trustedReverseProxiesDnsRefreshTimeout, trustedReverseProxies); } protected final Function httpsApiConnector() diff --git a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/ForwardedSecureRequestCustomizer.java b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/ForwardedSecureRequestCustomizer.java index 0a93ccfe6..d325f635b 100644 --- a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/ForwardedSecureRequestCustomizer.java +++ b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/ForwardedSecureRequestCustomizer.java @@ -16,6 +16,8 @@ package dev.dsf.common.jetty; import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; @@ -28,6 +30,7 @@ import org.slf4j.LoggerFactory; import de.hsheilbronn.mi.utils.crypto.io.PemReader; +import dev.dsf.common.config.network.InetSocketAddressMatcher; public class ForwardedSecureRequestCustomizer implements Customizer { @@ -40,15 +43,32 @@ public class ForwardedSecureRequestCustomizer implements Customizer private static final Logger logger = LoggerFactory.getLogger(ForwardedSecureRequestCustomizer.class); private final String clientCertHeaderName; + private final InetSocketAddressMatcher trustedProxyMatcher; - public ForwardedSecureRequestCustomizer(String clientCertHeaderName) + public ForwardedSecureRequestCustomizer(String clientCertHeaderName, InetSocketAddressMatcher trustedProxyMatcher) { this.clientCertHeaderName = Objects.requireNonNull(clientCertHeaderName, "clientCertHeaderName"); + this.trustedProxyMatcher = trustedProxyMatcher; } @Override public Request customize(Request request, Mutable responseHeaders) { + SocketAddress remote = request.getConnectionMetaData().getConnection().getEndPoint().getRemoteSocketAddress(); + if (!(remote instanceof InetSocketAddress)) + { + logger.warn("Ignoring {} header from untrusted reverse proxy, remote address not a InetSocketAddress", + clientCertHeaderName); + return request; + } + + if (!trustedProxyMatcher.matches((InetSocketAddress) remote)) + { + logger.warn("Ignoring {} header from untrusted reverse proxy {}", clientCertHeaderName, + ((InetSocketAddress) remote).getAddress().getHostAddress()); + return request; + } + X509Certificate clientCert = getClientCert(request); if (clientCert != null) diff --git a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/JettyServer.java b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/JettyServer.java index e32e03336..9da776c0b 100644 --- a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/JettyServer.java +++ b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/JettyServer.java @@ -21,6 +21,7 @@ import java.net.StandardSocketOptions; import java.nio.channels.ServerSocketChannel; import java.security.KeyStore; +import java.time.Duration; import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -56,6 +57,11 @@ import de.hsheilbronn.mi.utils.crypto.cert.CertificateFormatter.X500PrincipalFormat; import de.hsheilbronn.mi.utils.crypto.keystore.KeyStoreFormatter; +import dev.dsf.common.config.network.DynamicHostnameMatcher; +import dev.dsf.common.config.network.HostSpecParser.HostSpec; +import dev.dsf.common.config.network.InetSocketAddressMatcher; +import dev.dsf.common.config.network.InetSocketAddressMatcherList; +import dev.dsf.common.config.network.StaticCidrMatcher; import jakarta.servlet.ServletContainerInitializer; import jakarta.servlet.ServletContext; @@ -140,13 +146,15 @@ public static Function statusConnector(ServerSocketChan } public static Function httpConnector(String host, int port, - String clientCertificateHeaderName) + String clientCertificateHeaderName, Duration trustedReverseProxiesDnsRefreshTimeout, + List trustedReverseProxies) { return server -> { ServerConnector connector = new ServerConnector(server, - httpConnectionFactory(new ForwardedRequestCustomizer(), - new ForwardedSecureRequestCustomizer(clientCertificateHeaderName))); + httpConnectionFactory(new ForwardedRequestCustomizer(), new ForwardedSecureRequestCustomizer( + clientCertificateHeaderName, + createTrustedProxyMatcher(trustedReverseProxiesDnsRefreshTimeout, trustedReverseProxies)))); connector.setHost(host); connector.setPort(port); @@ -155,7 +163,8 @@ public static Function httpConnector(String host, int p } public static Function httpConnector(ServerSocketChannel channel, - String clientCertificateHeaderName) + String clientCertificateHeaderName, Duration trustedReverseProxiesDnsRefreshTimeout, + List trustedReverseProxies) { return server -> { @@ -163,7 +172,9 @@ public static Function httpConnector(ServerSocketChanne { ServerConnector connector = new ServerConnector(server, httpConnectionFactory(new ForwardedRequestCustomizer(), - new ForwardedSecureRequestCustomizer(clientCertificateHeaderName))); + new ForwardedSecureRequestCustomizer(clientCertificateHeaderName, + createTrustedProxyMatcher(trustedReverseProxiesDnsRefreshTimeout, + trustedReverseProxies)))); connector.open(channel); setHostAndPort(channel, connector); @@ -179,6 +190,40 @@ public static Function httpConnector(ServerSocketChanne }; } + private static InetSocketAddressMatcher createTrustedProxyMatcher(Duration trustedReverseProxiesDnsRefreshTimeout, + List trustedReverseProxies) + { + if (trustedReverseProxies == null) + { + logger.warn("Trusted reverse proxy matcher disabled"); + + return InetSocketAddressMatcher.ALL; + } + else + { + InetSocketAddressMatcher matcher = new InetSocketAddressMatcherList( + trustedReverseProxies.stream().map(toMatcher(trustedReverseProxiesDnsRefreshTimeout))); + + logger.info("Trusted reverse proxy matcher config: {}", matcher.toString()); + + return matcher; + } + } + + private static Function toMatcher( + Duration trustedReverseProxiesDnsRefreshTimeout) + { + return hostSpec -> + { + if (hostSpec.isIp()) + return StaticCidrMatcher.of(hostSpec); + else if (hostSpec.isDomainOrWildcard()) + return DynamicHostnameMatcher.of(trustedReverseProxiesDnsRefreshTimeout, hostSpec); + else + throw new IllegalArgumentException("hostSpec not supported"); + }; + } + public static Function httpsConnector(ServerSocketChannel channel, KeyStore clientCertificateTrustStore, KeyStore serverCertificateKeyStore, char[] keyStorePassword, boolean needClientAuth) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java index 87573ad30..46957ce9f 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java @@ -46,7 +46,7 @@ import de.hsheilbronn.mi.utils.crypto.cert.CertificateValidator; import de.hsheilbronn.mi.utils.crypto.io.PemReader; -import dev.dsf.common.config.AbstractCertificateConfig; +import dev.dsf.common.config.AbstractCertificateAndProxyConfig; import dev.dsf.common.config.ProxyConfig; import dev.dsf.common.config.ProxyConfigImpl; import dev.dsf.common.db.migration.DbMigratorConfig; @@ -56,7 +56,7 @@ @Configuration @PropertySource(value = "file:conf/config.properties", encoding = "UTF-8", ignoreResourceNotFound = true) -public class PropertiesConfig extends AbstractCertificateConfig implements InitializingBean +public class PropertiesConfig extends AbstractCertificateAndProxyConfig implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(PropertiesConfig.class); From d9f40e09cf90f095f2c9b6b867f8c5f5a8b52edf Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Thu, 13 Aug 2026 03:12:07 +0200 Subject: [PATCH 27/56] extends no-proxy config to allow for multi wildcard, IP and CIDR rules - The "dev.dsf.proxy.noProxy" config property can now be configured with exact domains `example.com`, one level wildcard sub-domains `*.example.com`, one or more level wildcard sub-domains `**.example.com`, IP-addresses and CIDR Networks. - IP/CIDR rules are applied if the target URL is specified as an IP-address. - IPv6 addresses and CIDR networks need to be specified in square brackets to allow for the optional specification of a target port. - Examples: sub.exact.com:80, *.one.level.wildcard.com, **.multilevel.com:443, 192.168.1.1, 192.168.1.0/24:80, [2001:db8::1]:443, [2001:db8::/32] - If no port is specified the target port is ignored. --- .../bpe/spring/config/PropertiesConfig.java | 3 + .../dsf/common/config/ProxyConfigImpl.java | 84 +++-- .../config/network/StaticHostnameMatcher.java | 183 ++++++++++ .../dsf/common/config/ProxyConfigTest.java | 42 +-- .../network/StaticHostnameMatcherTest.java | 314 ++++++++++++++++++ .../common/config/AbstractJettyConfig.java | 4 +- .../fhir/spring/config/PropertiesConfig.java | 3 + 7 files changed, 575 insertions(+), 58 deletions(-) create mode 100644 dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticHostnameMatcher.java create mode 100644 dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/StaticHostnameMatcherTest.java diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java index 27435d59f..c27c04300 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java @@ -51,6 +51,7 @@ import dev.dsf.common.config.AbstractCertificateAndProxyConfig; import dev.dsf.common.config.ProxyConfig; import dev.dsf.common.config.ProxyConfigImpl; +import dev.dsf.common.config.network.HostSpecParser.HostSpec; import dev.dsf.common.db.migration.DbMigratorConfig; import dev.dsf.common.docker.secrets.DockerSecretsPropertySourceFactory; import dev.dsf.common.documentation.Documentation; @@ -993,6 +994,8 @@ public int getJettyStatusConnectorPort() @Bean public ProxyConfig proxyConfig() { + List proxyNoProxy = parseHostSpecList("dev.dsf.proxy.noProxy", this.proxyNoProxy, false); + return new ProxyConfigImpl(proxyUrl, proxyUsername, proxyPassword, proxyNoProxy); } diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/ProxyConfigImpl.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/ProxyConfigImpl.java index e58510fbc..6261c1a9e 100644 --- a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/ProxyConfigImpl.java +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/ProxyConfigImpl.java @@ -15,39 +15,59 @@ */ package dev.dsf.common.config; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.Optional; +import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; +import dev.dsf.common.config.network.HostSpecParser.HostSpec; +import dev.dsf.common.config.network.InetSocketAddressMatcher; +import dev.dsf.common.config.network.InetSocketAddressMatcherList; +import dev.dsf.common.config.network.StaticCidrMatcher; +import dev.dsf.common.config.network.StaticHostnameMatcher; + public class ProxyConfigImpl implements ProxyConfig, InitializingBean { private static final Logger logger = LoggerFactory.getLogger(ProxyConfigImpl.class); + private static final Pattern IPV4_LITERAL = Pattern.compile("^[0-9]{1,3}(?:\\.[0-9]{1,3}){3}$"); + private final String url; private final String username; private final char[] password; - private final List noProxyUrls = new ArrayList<>(); - public ProxyConfigImpl(String url, String username, char[] password, Collection noProxyUrls) + private final List noProxyUrls; + private final InetSocketAddressMatcher noProxyMatcher; + + public ProxyConfigImpl(String url, String username, char[] password, Collection noProxyUrls) { this.url = nullIfUrlInvalid(url); this.username = username; this.password = password; - if (noProxyUrls != null) - this.noProxyUrls.addAll(noProxyUrls.stream().filter(s -> s != null && !s.isBlank()).toList()); + this.noProxyUrls = noProxyUrls == null ? List.of() : List.copyOf(noProxyUrls); + + noProxyMatcher = new InetSocketAddressMatcherList(this.noProxyUrls.stream().map(ProxyConfigImpl::toMatcher)); + } + private static InetSocketAddressMatcher toMatcher(HostSpec hostSpec) + { + if (hostSpec.isIp()) + return StaticCidrMatcher.of(hostSpec); + else if (hostSpec.isDomainOrWildcard()) + return StaticHostnameMatcher.of(hostSpec); + else + throw new IllegalArgumentException("hostSpec not supported"); } private static String nullIfUrlInvalid(String url) @@ -82,7 +102,7 @@ else if (!"http".equals(u.getProtocol()) && !"https".equals(u.getProtocol())) public void afterPropertiesSet() throws Exception { logger.info("Forward proxy config: {url: {}, username: {}, password: {}, no-proxy: {}}", url, username, - password != null ? "***" : "null", noProxyUrls); + password != null ? "***" : "null", noProxyMatcher.toString()); } @Override @@ -94,7 +114,7 @@ public String getUrl() @Override public boolean isEnabled() { - return url != null && !noProxyUrls.contains("*"); + return url != null; } @Override @@ -121,39 +141,53 @@ public char[] getPassword() @Override public List getNoProxyUrls() { - return Collections.unmodifiableList(noProxyUrls); + return noProxyUrls.stream().map(HostSpec::toString).toList(); } @Override public boolean isNoProxyUrl(String targetUrl) { - if (noProxyUrls.contains("*")) - return true; - if (targetUrl == null || targetUrl.isBlank()) return false; + Optional targetAddress = toSocketAddress(targetUrl); + if (targetAddress.isEmpty()) + return false; + else + return noProxyMatcher.matches(targetAddress.get()); + } + + private boolean isLikelyIpLiteral(String host) + { + return host.indexOf(':') >= 0 || IPV4_LITERAL.matcher(host).matches(); + } + + private Optional toSocketAddress(String targetUrl) + { try { URI u = new URI(targetUrl); + String scheme = u.getScheme(); String host = u.getHost(); - if (host == null) + + if (scheme == null || host == null) { - logger.debug("Given targetUrl '{}' is malformed, no host value", targetUrl); - return false; + logger.debug("Invalid target URL: scheme null or host null"); + return Optional.empty(); } - String subHost = Stream.of(u.getHost().split("\\.")).skip(1).collect(Collectors.joining(".")); - int port = u.getPort() == -1 ? getDefaultPort(u.getScheme()) : u.getPort(); + int port = u.getPort() >= 0 ? u.getPort() : getDefaultPort(scheme); - return noProxyUrls.stream().anyMatch(s -> s.equals(host) || s.equals(host + ":" + port) || s.equals(subHost) - || s.equals(subHost + ":" + port)); + if (isLikelyIpLiteral(host)) + return Optional.of(new InetSocketAddress(InetAddress.ofLiteral(host), port)); + else + return Optional.of(InetSocketAddress.createUnresolved(host, port)); } - catch (URISyntaxException e) + catch (URISyntaxException | IllegalArgumentException e) { - logger.debug("Given targetUrl '{}' is malformed: {}", targetUrl, e.getMessage()); - return false; + logger.debug("Invalid target URL: {}", e.getMessage()); + return Optional.empty(); } } @@ -163,7 +197,7 @@ private int getDefaultPort(String scheme) { case "http", "ws" -> 80; case "https", "wss" -> 443; - default -> throw new IllegalArgumentException("Schema " + scheme + " not supported"); + default -> throw new IllegalArgumentException("Scheme '" + scheme + "' not supported"); }; } } diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticHostnameMatcher.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticHostnameMatcher.java new file mode 100644 index 000000000..d33d987da --- /dev/null +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticHostnameMatcher.java @@ -0,0 +1,183 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.common.config.network; + +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import dev.dsf.common.config.network.HostSpecParser.HostSpec; + +public final class StaticHostnameMatcher implements InetSocketAddressMatcher +{ + public static InetSocketAddressMatcherList of(HostSpec... hostSpecs) + { + return of(hostSpecs == null ? List.of() : Arrays.asList(hostSpecs)); + } + + public static InetSocketAddressMatcherList of(Collection hostSpecs) + { + return new InetSocketAddressMatcherList(hostSpecs == null ? List.of() + : hostSpecs.stream().filter(HostSpec::isDomainOrWildcard).map(StaticHostnameMatcher::of).toList()); + } + + public static StaticHostnameMatcher of(HostSpec hostSpec) + { + Objects.requireNonNull(hostSpec, "hostSpec"); + + return new StaticHostnameMatcher(compile(hostSpec)); + } + + private final MatchPattern pattern; + + private StaticHostnameMatcher(MatchPattern pattern) + { + this.pattern = Objects.requireNonNull(pattern, "pattern"); + } + + private static MatchPattern compile(HostSpec hostSpec) + { + return switch (hostSpec.kind()) + { + case DOMAIN -> new ExactPattern(hostSpec.host(), hostSpec.port()); + case DOMAIN_WILDCARD_SINGLE_LEVEL -> new SingleLevelWildcardPattern(hostSpec.host(), hostSpec.port()); + case DOMAIN_WILDCARD_MULTI_LEVEL -> new MultiLevelWildcardPattern(hostSpec.host(), hostSpec.port()); + + default -> throw new IllegalArgumentException("hostSpec.kind '" + hostSpec.kind() + "' not supported"); + }; + } + + private static boolean portMatches(Integer expectedPort, int candidatePort) + { + return expectedPort == null || expectedPort == candidatePort; + } + + private static String toOptionalPortString(String hostname, Integer port) + { + return hostname + (port == null ? "" : ":" + port); + } + + private interface MatchPattern + { + boolean matches(String hostname, int port); + } + + /** + * Example: test.invalid, matches test.invalid, does not match foo.test.invalid + */ + private record ExactPattern(String hostname, Integer port) implements MatchPattern + { + @Override + public boolean matches(String candidateHostname, int candidatePort) + { + return hostname.equalsIgnoreCase(candidateHostname) && portMatches(port, candidatePort); + } + + @Override + public final String toString() + { + return toOptionalPortString(hostname, port); + } + } + + /** + * Example: *.test.invalid, matches foo.test.invalid, bar.test.invalid, does not match foo.bar.test.invalid, + * test.invalid + */ + private record SingleLevelWildcardPattern(String suffix, Integer port) implements MatchPattern + { + @Override + public boolean matches(String candidateHostname, int candidatePort) + { + if (!portMatches(port, candidatePort)) + return false; + + String prefix = subdomainPrefix(candidateHostname); + + return prefix != null && prefix.indexOf('.') < 0; + } + + private String subdomainPrefix(String candidateHostname) + { + if (candidateHostname.length() <= suffix.length() + 1) + return null; + + int suffixStart = candidateHostname.length() - suffix.length(); + + if (!candidateHostname.regionMatches(true, suffixStart, suffix, 0, suffix.length())) + return null; + + if (candidateHostname.charAt(suffixStart - 1) != '.') + return null; + + return candidateHostname.substring(0, suffixStart - 1); + } + + @Override + public final String toString() + { + return "*." + toOptionalPortString(suffix, port); + } + } + + /** + * Example: **.test.invalid, matches foo.test.invalid, bar.test.invalid, foo.bar.test.invalid, does not match + * test.invalid + */ + private record MultiLevelWildcardPattern(String suffix, Integer port) implements MatchPattern + { + @Override + public boolean matches(String candidateHostname, int candidatePort) + { + if (!portMatches(port, candidatePort)) + return false; + + if (candidateHostname.length() <= suffix.length() + 1) + return false; + + int suffixStart = candidateHostname.length() - suffix.length(); + + return candidateHostname.regionMatches(true, suffixStart, suffix, 0, suffix.length()) + && candidateHostname.charAt(suffixStart - 1) == '.'; + } + + @Override + public final String toString() + { + return "**." + toOptionalPortString(suffix, port); + } + } + + @Override + public boolean matches(InetSocketAddress address) + { + if (address == null) + return false; + + String hostname = address.getHostString(); + int port = address.getPort(); + + return pattern.matches(hostname, port); + } + + @Override + public String toString() + { + return pattern.toString(); + } +} \ No newline at end of file diff --git a/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/ProxyConfigTest.java b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/ProxyConfigTest.java index bed2ce56b..55c8ad55f 100644 --- a/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/ProxyConfigTest.java +++ b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/ProxyConfigTest.java @@ -15,6 +15,7 @@ */ package dev.dsf.common.config; +import static dev.dsf.common.config.network.HostSpecParser.parse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -26,6 +27,8 @@ import org.junit.Test; +import dev.dsf.common.config.network.HostSpecParser.HostSpec; + public class ProxyConfigTest { @Test @@ -55,7 +58,7 @@ public void testGetMethods() throws Exception String url = "http://proxy", username = "username"; char[] password = "password".toCharArray(); // Arrays.asList as we need a null element, not allowed in List.of - List noProxy = Arrays.asList(null, " ", "no-proxy"); + List noProxy = parse(Arrays.asList(null, " ", "no-proxy")); ProxyConfigImpl c = new ProxyConfigImpl(url, username, password, noProxy); assertEquals(url, c.getUrl()); @@ -70,16 +73,15 @@ public void testGetMethods() throws Exception public void testIsEnabled() throws Exception { assertTrue(new ProxyConfigImpl("http://proxy", null, null, null).isEnabled()); - assertTrue(new ProxyConfigImpl("http://proxy", null, null, List.of("foo")).isEnabled()); + assertTrue(new ProxyConfigImpl("http://proxy", null, null, List.of(parse("foo"))).isEnabled()); assertFalse(new ProxyConfigImpl(null, null, null, null).isEnabled()); - assertFalse(new ProxyConfigImpl("http://proxy", null, null, List.of("*")).isEnabled()); } @Test public void testIsEndabled() throws Exception { ProxyConfig proxyConfig = new ProxyConfigImpl("http://proxy", null, null, - List.of("foo.bar", "foo.bar.baz:8080", "test:1234")); + parse("foo.bar", "*.foo.bar", "foo.bar.baz:8080", "test:1234")); assertFalse(proxyConfig.isEnabled("http://foo.bar")); assertFalse(proxyConfig.isEnabled("http://foo.bar:8080")); @@ -87,7 +89,7 @@ public void testIsEndabled() throws Exception assertFalse(proxyConfig.isEnabled("https://foo.bar:8443")); assertFalse(proxyConfig.isEnabled("http://test.foo.bar")); assertFalse(proxyConfig.isEnabled("https://test.foo.bar:443")); - assertFalse(proxyConfig.isEnabled("https://test.test:1234")); + assertFalse(proxyConfig.isEnabled("https://test:1234")); assertFalse(proxyConfig.isEnabled("http://foo.bar.baz:8080")); assertFalse(proxyConfig.isEnabled("https://foo.bar.baz:8080")); @@ -96,41 +98,18 @@ public void testIsEndabled() throws Exception assertTrue(proxyConfig.isEnabled("http://bar.baz")); assertTrue(proxyConfig.isEnabled("https://bar.baz")); assertTrue(proxyConfig.isEnabled("https://test.test")); - } - - @Test - public void testIsEnabledAllNoProxy() throws Exception - { - ProxyConfig proxyConfig = new ProxyConfigImpl(null, null, null, List.of("*")); - - assertFalse(proxyConfig.isEnabled("http://foo.bar")); - assertFalse(proxyConfig.isEnabled("http://foo.bar:8080")); - assertFalse(proxyConfig.isEnabled("https://foo.bar")); - assertFalse(proxyConfig.isEnabled("https://foo.bar:8443")); - assertFalse(proxyConfig.isEnabled("http://test.foo.bar")); - assertFalse(proxyConfig.isEnabled("https://test.foo.bar:443")); - - assertFalse(proxyConfig.isEnabled("http://foo.bar.baz:8080")); - assertFalse(proxyConfig.isEnabled("https://foo.bar.baz:8080")); - assertFalse(proxyConfig.isEnabled("http://foo.bar.baz")); - assertFalse(proxyConfig.isEnabled("https://foo.bar.baz")); - assertFalse(proxyConfig.isEnabled("http://bar.baz")); - assertFalse(proxyConfig.isEnabled("https://bar.baz")); + assertTrue(proxyConfig.isEnabled("https://test.test:1234")); } @Test public void testIsEnabledNull() throws Exception { assertFalse(new ProxyConfigImpl("http://proxy", null, null, null).isEnabled(null)); - assertFalse(new ProxyConfigImpl("http://proxy", null, null, List.of("*")).isEnabled(null)); - assertFalse(new ProxyConfigImpl("http://proxy", null, null, null).isEnabled(null)); } @Test public void testIsEnabledBlank() throws Exception { - assertFalse(new ProxyConfigImpl("http://proxy", null, null, List.of("*")).isEnabled("")); - assertFalse(new ProxyConfigImpl("http://proxy", null, null, List.of("*")).isEnabled(" ")); assertFalse(new ProxyConfigImpl("http://proxy", null, null, null).isEnabled("")); assertFalse(new ProxyConfigImpl("http://proxy", null, null, null).isEnabled(" ")); } @@ -138,14 +117,13 @@ public void testIsEnabledBlank() throws Exception @Test public void testIsEnabledMalformedUrl() throws Exception { - assertFalse(new ProxyConfigImpl("http://proxy", null, null, List.of("*")).isEnabled("malformed")); assertTrue(new ProxyConfigImpl("http://proxy", null, null, null).isEnabled(":malformed")); assertTrue(new ProxyConfigImpl("http://proxy", null, null, null).isEnabled("malformed")); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIsEnabledSchemaNotSupported() throws Exception { - new ProxyConfigImpl("http://proxy", null, null, null).isEnabled("foo://bar"); + assertTrue(new ProxyConfigImpl("http://proxy", null, null, null).isEnabled("foo://bar")); } } diff --git a/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/StaticHostnameMatcherTest.java b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/StaticHostnameMatcherTest.java new file mode 100644 index 000000000..3b0f540d9 --- /dev/null +++ b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/StaticHostnameMatcherTest.java @@ -0,0 +1,314 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.common.config.network; + +import static dev.dsf.common.config.network.HostSpecParser.parse; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Test; + +public class StaticHostnameMatcherTest +{ + @Test + public void exactHostnameMatches() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("example.com")); + + assertMatches(matcher, "example.com", 80); + assertMatches(matcher, "EXAMPLE.COM", 80); + } + + @Test + public void exactHostnameDoesNotMatchDifferentHostname() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("example.com")); + + assertNotMatches(matcher, "other.com", 80); + assertNotMatches(matcher, "sub.example.com", 80); + } + + @Test + public void hostnameWithoutPortMatchesAnyPort() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("example.com")); + + assertMatches(matcher, "example.com", 0); + assertMatches(matcher, "example.com", 80); + assertMatches(matcher, "example.com", 443); + assertMatches(matcher, "example.com", 65535); + } + + @Test + public void exactHostnameAndPortMatches() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("example.com:443")); + + assertMatches(matcher, "example.com", 443); + assertMatches(matcher, "EXAMPLE.COM", 443); + } + + @Test + public void exactHostnameAndPortDoesNotMatchDifferentPort() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("example.com:443")); + + assertNotMatches(matcher, "example.com", 80); + assertNotMatches(matcher, "example.com", 444); + } + + @Test + public void singleLevelWildcardMatchesOneSubdomain() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("*.example.com")); + + assertMatches(matcher, "www.example.com", 80); + assertMatches(matcher, "api.example.com", 443); + assertMatches(matcher, "WWW.EXAMPLE.COM", 80); + } + + @Test + public void singleLevelWildcardDoesNotMatchBaseHostname() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("*.example.com")); + + assertNotMatches(matcher, "example.com", 80); + } + + @Test + public void singleLevelWildcardDoesNotMatchNestedSubdomain() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("*.example.com")); + + assertNotMatches(matcher, "foo.www.example.com", 80); + assertNotMatches(matcher, "a.b.example.com", 80); + } + + @Test + public void singleLevelWildcardDoesNotMatchSimilarSuffix() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("*.example.com")); + + assertNotMatches(matcher, "www.notexample.com", 80); + assertNotMatches(matcher, "www.example.com.evil.com", 80); + } + + @Test + public void singleLevelWildcardWithPortMatchesCorrectPort() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("*.example.com:443")); + + assertMatches(matcher, "www.example.com", 443); + assertNotMatches(matcher, "www.example.com", 80); + assertNotMatches(matcher, "foo.www.example.com", 443); + } + + @Test + public void recursiveWildcardMatchesOneOrMoreSubdomains() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("**.example.com")); + + assertMatches(matcher, "www.example.com", 80); + assertMatches(matcher, "api.example.com", 80); + assertMatches(matcher, "foo.www.example.com", 80); + assertMatches(matcher, "a.b.c.example.com", 80); + } + + @Test + public void recursiveWildcardDoesNotMatchBaseHostname() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("**.example.com")); + + assertNotMatches(matcher, "example.com", 80); + } + + @Test + public void recursiveWildcardDoesNotMatchSimilarSuffix() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("**.example.com")); + + assertNotMatches(matcher, "evil-example.com", 80); + assertNotMatches(matcher, "foo.example.com.evil.com", 80); + } + + @Test + public void recursiveWildcardWithPortMatchesCorrectPort() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("**.example.com:443")); + + assertMatches(matcher, "www.example.com", 443); + assertMatches(matcher, "foo.www.example.com", 443); + + assertNotMatches(matcher, "www.example.com", 80); + assertNotMatches(matcher, "foo.www.example.com", 8443); + assertNotMatches(matcher, "example.com", 443); + } + + @Test + public void leadingAndTrailingWhitespaceIsIgnored() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse(" example.com:443 ")); + + assertMatches(matcher, "example.com", 443); + } + + @Test + public void blankHostnamesAreIgnored() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse(" ", "", "\t", "example.com")); + + assertMatches(matcher, "example.com", 80); + } + + @Test + public void nullHostnameElementsAreIgnoredWhenUsingList() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse(Arrays.asList(null, "example.com", null))); + + assertMatches(matcher, "example.com", 80); + assertNotMatches(matcher, "other.com", 80); + } + + @Test + public void nullListProducesMatcherWithNoPatterns() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse((java.util.List) null)); + + assertNotMatches(matcher, "example.com", 80); + } + + @Test + public void emptyListProducesMatcherWithNoPatterns() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse(Collections.emptyList())); + + assertNotMatches(matcher, "example.com", 80); + } + + @Test + public void nullAddressDoesNotMatch() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("example.com")); + + assertFalse(matcher.matches(null)); + } + + @Test + public void portZeroIsValid() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("example.com:0")); + + assertMatches(matcher, "example.com", 0); + assertNotMatches(matcher, "example.com", 1); + } + + @Test + public void maximumPortIsValid() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("example.com:65535")); + + assertMatches(matcher, "example.com", 65535); + } + + @Test + public void portAboveMaximumIsRejected() + { + assertInvalid("example.com:65536"); + } + + @Test + public void nonNumericPortIsRejected() + { + assertInvalid("example.com:abc"); + } + + @Test + public void negativePortIsRejected() + { + assertInvalid("example.com:-1"); + } + + @Test + public void emptyWildcardSuffixIsRejected() + { + assertInvalid("*."); + assertInvalid("**."); + } + + @Test + public void singleColonIsRejected() + { + assertInvalid(":"); + } + + @Test + public void colonWithoutPortIsRejected() + { + assertInvalid("example.com:"); + } + + @Test + public void multipleColonsAreRejected() + { + assertInvalid("example.com::443"); + } + + @Test + public void multiplePatternsMatchIfAnyPatternMatches() + { + InetSocketAddressMatcher matcher = StaticHostnameMatcher.of(parse("localhost"), parse("*.example.com"), + parse("server.example.org:443")); + + assertMatches(matcher, "localhost", 80); + assertMatches(matcher, "localhost", 443); + assertMatches(matcher, "www.example.com", 80); + assertMatches(matcher, "www.example.com", 443); + assertMatches(matcher, "server.example.org", 443); + + assertNotMatches(matcher, "server.example.org", 80); + assertNotMatches(matcher, "other.org", 443); + } + + private static void assertMatches(InetSocketAddressMatcher matcher, String hostname, int port) + { + assertTrue("Expected " + hostname + ":" + port + " to match", + matcher.matches(InetSocketAddress.createUnresolved(hostname, port))); + } + + private static void assertNotMatches(InetSocketAddressMatcher matcher, String hostname, int port) + { + assertFalse("Expected " + hostname + ":" + port + " not to match", + matcher.matches(InetSocketAddress.createUnresolved(hostname, port))); + } + + private static void assertInvalid(String pattern) + { + try + { + StaticHostnameMatcher.of(parse(pattern)); + fail("Expected IllegalArgumentException for: " + pattern); + } + catch (IllegalArgumentException expected) + { + // Expected. + } + } +} diff --git a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java index d90427ad2..91d66597f 100644 --- a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java +++ b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java @@ -239,7 +239,7 @@ public abstract class AbstractJettyConfig extends AbstractCertificateAndProxyCon @Value("${dev.dsf.proxy.password:#{null}}") private char[] proxyPassword; - @Documentation(description = "Forward proxy no-proxy list, entries will match exactly or against (one level) sub-domains, if no port is specified - all ports are matched; comma or space separated list, YAML block scalars supported", example = "foo.bar, test.com:8080") + @Documentation(description = "Forward proxy no-proxy list: Target URLs will match exact domains `example.com`, against one level sub-domains `*.example.com`, one or more level sub-domains `**.example.com`, against IP-addresses and CIDR Networks if the target URL is specified as IP-address (IPv6 in square brackets), if no port is specified - all ports are matched; comma or space separated list, YAML block scalars supported", example = "sub.exact.com:80, *.one.level.wildcard.com, **.multilevel.com:443, 192.168.1.1, 192.168.1.0/24:80, [2001:db8::1]:443, [2001:db8::/32]") @Value("#{'${dev.dsf.proxy.noProxy:}'.trim().split('[,\\s]+')}") private List proxyNoProxy; @@ -519,6 +519,8 @@ private Proxy oidcClientProxy() @Lazy public ProxyConfig proxyConfig() { + List proxyNoProxy = parseHostSpecList("dev.dsf.proxy.noProxy", this.proxyNoProxy, false); + return new ProxyConfigImpl(proxyUrl, proxyUsername, proxyPassword, proxyNoProxy); } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java index 46957ce9f..7b3e86a48 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java @@ -49,6 +49,7 @@ import dev.dsf.common.config.AbstractCertificateAndProxyConfig; import dev.dsf.common.config.ProxyConfig; import dev.dsf.common.config.ProxyConfigImpl; +import dev.dsf.common.config.network.HostSpecParser.HostSpec; import dev.dsf.common.db.migration.DbMigratorConfig; import dev.dsf.common.docker.secrets.DockerSecretsPropertySourceFactory; import dev.dsf.common.documentation.Documentation; @@ -420,6 +421,8 @@ public boolean getOidcBearerTokenEnabled() @Bean public ProxyConfig proxyConfig() { + List proxyNoProxy = parseHostSpecList("dev.dsf.proxy.noProxy", this.proxyNoProxy, false); + return new ProxyConfigImpl(proxyUrl, proxyUsername, proxyPassword, proxyNoProxy); } From d3686b483b0d282a0ef87589183f26cc37cc2ced Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 17 Aug 2026 12:38:33 +0200 Subject: [PATCH 28/56] missing override annotations --- .../src/main/java/dev/dsf/bpe/config/FhirClientConfigYaml.java | 3 +++ .../dev/dsf/bpe/config/FhirClientConfigYamlReaderTest.java | 1 + .../dev/dsf/common/config/network/DynamicHostnameMatcher.java | 1 + 3 files changed, 5 insertions(+) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/config/FhirClientConfigYaml.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/config/FhirClientConfigYaml.java index e1bb2f83c..c64baa666 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/config/FhirClientConfigYaml.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/config/FhirClientConfigYaml.java @@ -399,6 +399,7 @@ public OidcAuth(@JsonProperty(OidcAuth.PROPERTY_BASE_URL) String baseUrl, this.verifyAuthorizedParty = verifyAuthorizedParty; } + @Override public String baseUrl() { if (baseUrl != null && baseUrl.endsWith("/")) @@ -407,6 +408,7 @@ public String baseUrl() return baseUrl; } + @Override public String discoveryPath() { if (discoveryPath != null && !discoveryPath.startsWith("/")) @@ -508,6 +510,7 @@ public FhirClientConfigYaml(@JsonProperty(FhirClientConfigYaml.PROPERTY_BASE_URL this.oidcAuth = oidcAuth; } + @Override public String baseUrl() { if (baseUrl != null && baseUrl.endsWith("/")) diff --git a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/config/FhirClientConfigYamlReaderTest.java b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/config/FhirClientConfigYamlReaderTest.java index 7cea0bbec..49297f5fe 100644 --- a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/config/FhirClientConfigYamlReaderTest.java +++ b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/config/FhirClientConfigYamlReaderTest.java @@ -148,6 +148,7 @@ public class FhirClientConfigYamlReaderTest private static record YamlAndErrorCount(String yaml, int errorCount) { + @Override public String yaml() { return replaceTestProperties(yaml); diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/DynamicHostnameMatcher.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/DynamicHostnameMatcher.java index 73186fd0e..9c2095fd9 100644 --- a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/DynamicHostnameMatcher.java +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/DynamicHostnameMatcher.java @@ -112,6 +112,7 @@ private DynamicHostnameMatcher(Duration refreshTimeout, String hostname) refresh(); } + @Override public boolean matches(InetSocketAddress address) { if (address == null || address.isUnresolved()) From 06e7810327fabc00ef7b31a5cbd6364eedf67b0d Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 17 Aug 2026 12:57:33 +0200 Subject: [PATCH 29/56] null guard, additional must reject illegal values test --- .../common/config/network/HostSpecParser.java | 4 ++-- .../config/network/HostSpecParserTest.java | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/HostSpecParser.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/HostSpecParser.java index 3f41a35a0..dd0314b31 100644 --- a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/HostSpecParser.java +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/HostSpecParser.java @@ -512,9 +512,9 @@ public static record HostSpec(Kind kind, String host, Integer prefixLength, Inte Objects.requireNonNull(kind, "kind"); Objects.requireNonNull(host, "host"); - if (kind == Kind.IPV4 && prefixLength != MAX_IPV4_PREFIX) + if (kind == Kind.IPV4 && (prefixLength == null || prefixLength != MAX_IPV4_PREFIX)) throw new IllegalArgumentException("Invalid prefix length: " + prefixLength); - else if (kind == Kind.IPV6 && prefixLength != MAX_IPV6_PREFIX) + else if (kind == Kind.IPV6 && (prefixLength == null || prefixLength != MAX_IPV6_PREFIX)) throw new IllegalArgumentException("Invalid prefix length: " + prefixLength); else if (prefixLength != null) { diff --git a/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/HostSpecParserTest.java b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/HostSpecParserTest.java index fc893335b..afd9119e5 100644 --- a/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/HostSpecParserTest.java +++ b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/HostSpecParserTest.java @@ -17,6 +17,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import java.util.Arrays; @@ -305,4 +306,24 @@ public void parseRejectsNulCharacter() String input = "foo" + '\0' + "bar.example"; HostSpecParser.parse(input); } + + @Test + public void hostSpecMustRehectIllegalValues() throws Exception + { + assertThrows(NullPointerException.class, () -> new HostSpec(null, null, null, null)); + assertThrows(NullPointerException.class, () -> new HostSpec(Kind.DOMAIN, null, null, null)); + assertThrows(IllegalArgumentException.class, () -> new HostSpec(Kind.DOMAIN, "host", 666, null)); + assertThrows(IllegalArgumentException.class, () -> new HostSpec(Kind.DOMAIN, "host", null, -1)); + assertThrows(IllegalArgumentException.class, () -> new HostSpec(Kind.DOMAIN, "host", null, 65_535 + 1)); + assertThrows(IllegalArgumentException.class, () -> new HostSpec(Kind.IPV4, "127.0.0.1", null, null)); + assertThrows(IllegalArgumentException.class, () -> new HostSpec(Kind.IPV4, "127.0.0.1", 32 + 1, null)); + assertThrows(IllegalArgumentException.class, () -> new HostSpec(Kind.IPV4, "127.0.0.1", 32 - 1, null)); + assertThrows(IllegalArgumentException.class, () -> new HostSpec(Kind.IPV4_CIDR, "127.0.0.1/32", 32 + 1, null)); + assertThrows(IllegalArgumentException.class, () -> new HostSpec(Kind.IPV4_CIDR, "127.0.0.1/32", -1, null)); + assertThrows(IllegalArgumentException.class, () -> new HostSpec(Kind.IPV6, "[::1]", null, null)); + assertThrows(IllegalArgumentException.class, () -> new HostSpec(Kind.IPV6, "[::1]", 128 + 1, null)); + assertThrows(IllegalArgumentException.class, () -> new HostSpec(Kind.IPV6, "[::1]", 128 - 1, null)); + assertThrows(IllegalArgumentException.class, () -> new HostSpec(Kind.IPV6_CIDR, "[::1/128]", 128 + 1, null)); + assertThrows(IllegalArgumentException.class, () -> new HostSpec(Kind.IPV6_CIDR, "[::1/128]", -1, null)); + } } \ No newline at end of file From eb39fe40ebe13d66a22347ef6e21bc9f913fb66e Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 17 Aug 2026 12:59:00 +0200 Subject: [PATCH 30/56] removed comma from javadoc --- .../dsf/common/config/network/InetSocketAddressMatcherList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcherList.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcherList.java index 804758caf..6d02bee28 100644 --- a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcherList.java +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcherList.java @@ -30,7 +30,7 @@ public class InetSocketAddressMatcherList implements InetSocketAddressMatcher private final List networks; /** - * @param networks, + * @param networks * null values are ignored */ public InetSocketAddressMatcherList(InetSocketAddressMatcher... networks) From 44b89cd20b1454a06b0f6a994deff87607740f5b Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 17 Aug 2026 13:17:55 +0200 Subject: [PATCH 31/56] improved constructors, added must filter null values test --- .../network/InetSocketAddressMatcherList.java | 12 ++++++------ .../network/InetSocketAddressMatcherListTest.java | 14 +++++++++++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcherList.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcherList.java index 6d02bee28..7a2b517ca 100644 --- a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcherList.java +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/InetSocketAddressMatcherList.java @@ -42,19 +42,19 @@ public InetSocketAddressMatcherList(InetSocketAddressMatcher... networks) * @param networks * may be null, null values are filtered */ - public InetSocketAddressMatcherList(Stream networks) + public InetSocketAddressMatcherList(Collection networks) { - this(networks == null ? null : networks.filter(Objects::nonNull).toList()); + this(networks == null ? null : networks.stream()); } /** * @param networks * may be null, null values are filtered */ - // null value filter in #matches(InetSocketAddress) - public InetSocketAddressMatcherList(Collection networks) + public InetSocketAddressMatcherList(Stream networks) { - this.networks = networks == null ? List.of() : List.copyOf(networks); + this.networks = networks == null ? List.of() + : networks.filter(Objects::nonNull).map(InetSocketAddressMatcher.class::cast).toList(); } @Override @@ -63,7 +63,7 @@ public boolean matches(InetSocketAddress address) if (address == null) return false; - return networks.stream().filter(Objects::nonNull).anyMatch(n -> n.matches(address)); + return networks.stream().anyMatch(n -> n.matches(address)); } public List getNetworks() diff --git a/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/InetSocketAddressMatcherListTest.java b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/InetSocketAddressMatcherListTest.java index d87ec43d9..036d4a3ad 100644 --- a/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/InetSocketAddressMatcherListTest.java +++ b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/InetSocketAddressMatcherListTest.java @@ -15,11 +15,10 @@ */ package dev.dsf.common.config.network; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import java.net.InetSocketAddress; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.stream.Stream; @@ -35,6 +34,15 @@ public void constructorAcceptsNull() throws Exception new InetSocketAddressMatcherList((Collection) null); } + @Test + public void constructorMustFilterNullValues() throws Exception + { + InetSocketAddressMatcherList m = new InetSocketAddressMatcherList( + Arrays.asList(null, InetSocketAddressMatcher.ALL)); + assertNotNull(m.getNetworks()); + assertEquals(1, m.getNetworks().size()); + } + @Test public void getNetworksReturnsUnmodifiableList() throws Exception { From c1626b2b2356ec299f2bd0859817d234ed4ec3ae Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 18 Aug 2026 18:32:31 +0200 Subject: [PATCH 32/56] added recommendation to disable for Kubernetes deployments --- .../main/java/dev/dsf/common/config/AbstractJettyConfig.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java index 91d66597f..0402bc78b 100644 --- a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java +++ b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java @@ -127,7 +127,9 @@ public abstract class AbstractJettyConfig extends AbstractCertificateAndProxyCon @Documentation(description = "Defines allowed source IPs for the reverse proxy, supported definitions: by hostname - resolved periodically (see *DEV_DSF_SERVER_AUTH_TRUST_REVERSE_PROXY_HOSTNAME_REFRESH_TIMEOUT*), by single IPv4 or IPv6 address, by IPv4 CIDR or IPv6 CIDR network; comma or space separated list, YAML block scalars supported; use `" + HOST_SPEC_LIST_DISABLED - + "` to allow all incoming IP addresses", example = "proxy, ingress.cluster.local, 192.168.1.1, 192.168.1.0/24, [2001:db8::1], [2001:db8::/32]") + + "` to allow all incoming IP addresses", example = "proxy, ingress.cluster.local, 192.168.1.1, 192.168.1.0/24, [2001:db8::1], [2001:db8::/32]", recommendation = "In Kubernetes deployments set `" + + HOST_SPEC_LIST_DISABLED + + "` and use [Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies)") @Value("#{'${dev.dsf.server.auth.trust.reverse.proxy:proxy}'.trim().split('[,\\s]+')}") private List trustedReverseProxies; From 0dde8307021ac219309512f63d79bc6600c232c0 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 18:33:05 +0200 Subject: [PATCH 33/56] typo fix Acces -> Access --- .../dev/dsf/fhir/authorization/read/ReadAccessHelperImpl.java | 4 ++-- .../java/dev/dsf/bpe/v2/service/ReadAccessHelperImpl.java | 4 ++-- .../dev/dsf/fhir/authorization/read/ReadAccessHelperImpl.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/authorization/read/ReadAccessHelperImpl.java b/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/authorization/read/ReadAccessHelperImpl.java index 09c4961ec..b5579ca89 100644 --- a/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/authorization/read/ReadAccessHelperImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/authorization/read/ReadAccessHelperImpl.java @@ -352,10 +352,10 @@ private boolean isValidOrganizationReadAccessTag(Coding coding, .filter(e -> EXTENSION_READ_ACCESS_ORGANIZATION.equals(e.getUrl())).collect(Collectors.toList()); return coding.hasExtension() && exts.size() == 1 - && isValidExtensionReadAccesOrganization(exts.get(0), organizationWithIdentifierExists); + && isValidExtensionReadAccessOrganization(exts.get(0), organizationWithIdentifierExists); } - private boolean isValidExtensionReadAccesOrganization(Extension extension, + private boolean isValidExtensionReadAccessOrganization(Extension extension, Predicate organizationWithIdentifierExists) { return extension.hasValue() && extension.getValue() instanceof Identifier value diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/ReadAccessHelperImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/ReadAccessHelperImpl.java index a7061a045..993b34b87 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/ReadAccessHelperImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/ReadAccessHelperImpl.java @@ -364,10 +364,10 @@ private boolean isValidOrganizationReadAccessTag(Coding coding, .filter(e -> EXTENSION_READ_ACCESS_ORGANIZATION.equals(e.getUrl())).collect(Collectors.toList()); return coding.hasExtension() && exts.size() == 1 - && isValidExtensionReadAccesOrganization(exts.get(0), organizationWithIdentifierExists); + && isValidExtensionReadAccessOrganization(exts.get(0), organizationWithIdentifierExists); } - private boolean isValidExtensionReadAccesOrganization(Extension extension, + private boolean isValidExtensionReadAccessOrganization(Extension extension, Predicate organizationWithIdentifierExists) { return extension.hasValue() && extension.getValue() instanceof Identifier value diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/read/ReadAccessHelperImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/read/ReadAccessHelperImpl.java index 09c4961ec..b5579ca89 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/read/ReadAccessHelperImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/read/ReadAccessHelperImpl.java @@ -352,10 +352,10 @@ private boolean isValidOrganizationReadAccessTag(Coding coding, .filter(e -> EXTENSION_READ_ACCESS_ORGANIZATION.equals(e.getUrl())).collect(Collectors.toList()); return coding.hasExtension() && exts.size() == 1 - && isValidExtensionReadAccesOrganization(exts.get(0), organizationWithIdentifierExists); + && isValidExtensionReadAccessOrganization(exts.get(0), organizationWithIdentifierExists); } - private boolean isValidExtensionReadAccesOrganization(Extension extension, + private boolean isValidExtensionReadAccessOrganization(Extension extension, Predicate organizationWithIdentifierExists) { return extension.hasValue() && extension.getValue() instanceof Identifier value From 86da70e01637d8f2e1f6a409746ad7935476f033 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 18:36:35 +0200 Subject: [PATCH 34/56] fixed log message duration formatting, toString -> toSeconds --- .../client/AbstractFhirWebserviceClientJerseyWithRetry.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/AbstractFhirWebserviceClientJerseyWithRetry.java b/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/AbstractFhirWebserviceClientJerseyWithRetry.java index a336cbbb4..04e73906f 100644 --- a/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/AbstractFhirWebserviceClientJerseyWithRetry.java +++ b/dsf-bpe/dsf-bpe-process-api-v1-impl/src/main/java/dev/dsf/fhir/client/AbstractFhirWebserviceClientJerseyWithRetry.java @@ -65,7 +65,8 @@ else if (nTimes != RetryClient.RETRY_FOREVER) if (tryNumber < nTimes || nTimes == RetryClient.RETRY_FOREVER) { logger.warn("Caught {} - {}; trying again in {}s{}", e.getClass().getName(), e.getMessage(), - delay, nTimes == RetryClient.RETRY_FOREVER ? " (retry " + (tryNumber + 1) + ")" : ""); + delay.toSeconds(), + nTimes == RetryClient.RETRY_FOREVER ? " (retry " + (tryNumber + 1) + ")" : ""); try { From 42b1655f911c50c9be7f476a7a8c1cf13f6067e0 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 18:39:40 +0200 Subject: [PATCH 35/56] typo fix emtpy -> empty --- .../java/dev/dsf/bpe/v1/activity/AbstractTaskMessageSend.java | 2 +- .../java/dev/dsf/bpe/v1/activity/AbstractTaskMessageSend.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-bpe/dsf-bpe-process-api-v1-operaton/src/main/java/dev/dsf/bpe/v1/activity/AbstractTaskMessageSend.java b/dsf-bpe/dsf-bpe-process-api-v1-operaton/src/main/java/dev/dsf/bpe/v1/activity/AbstractTaskMessageSend.java index ef3862e41..dfe03d0e3 100644 --- a/dsf-bpe/dsf-bpe-process-api-v1-operaton/src/main/java/dev/dsf/bpe/v1/activity/AbstractTaskMessageSend.java +++ b/dsf-bpe/dsf-bpe-process-api-v1-operaton/src/main/java/dev/dsf/bpe/v1/activity/AbstractTaskMessageSend.java @@ -287,7 +287,7 @@ protected void handleSendTaskError(DelegateExecution execution, Variables variab exception.getMessage()); } - // if we are not a multi instance message send task or all sends have failed (targets emtpy) + // if we are not a multi instance message send task or all sends have failed (targets empty) else { logger.debug("Error while executing Task message send {}", getClass().getName(), exception); diff --git a/dsf-bpe/dsf-bpe-process-api-v1/src/main/java/dev/dsf/bpe/v1/activity/AbstractTaskMessageSend.java b/dsf-bpe/dsf-bpe-process-api-v1/src/main/java/dev/dsf/bpe/v1/activity/AbstractTaskMessageSend.java index 8d20b3dbf..7d562b8d9 100644 --- a/dsf-bpe/dsf-bpe-process-api-v1/src/main/java/dev/dsf/bpe/v1/activity/AbstractTaskMessageSend.java +++ b/dsf-bpe/dsf-bpe-process-api-v1/src/main/java/dev/dsf/bpe/v1/activity/AbstractTaskMessageSend.java @@ -287,7 +287,7 @@ protected void handleSendTaskError(DelegateExecution execution, Variables variab exception.getMessage()); } - // if we are not a multi instance message send task or all sends have failed (targets emtpy) + // if we are not a multi instance message send task or all sends have failed (targets empty) else { logger.debug("Error while executing Task message send {}", getClass().getName(), exception); From 9a37085c21fc3b0cacfaddf6722f18bf77a785a8 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 18:41:39 +0200 Subject: [PATCH 36/56] typo fix Progess -> Progress --- .../java/dev/dsf/bpe/engine/DelegateProviderImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/DelegateProviderImpl.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/DelegateProviderImpl.java index bdc262ae5..2609d7223 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/DelegateProviderImpl.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/DelegateProviderImpl.java @@ -192,7 +192,7 @@ private void stopProcess(ProcessIdAndVersion processIdAndVersion, DelegateExecut try { - searchInProgessTasks(execution.getBusinessKey()).forEach(task -> + searchInProgressTasks(execution.getBusinessKey()).forEach(task -> { try { @@ -226,7 +226,7 @@ private void stopProcess(ProcessIdAndVersion processIdAndVersion, DelegateExecut "Plugin for process " + processIdAndVersion + " not found"); } - protected final Stream searchInProgessTasks(String businessKey) + protected final Stream searchInProgressTasks(String businessKey) { List> resources = new ArrayList<>(); @@ -234,7 +234,7 @@ protected final Stream searchInProgessTasks(String businessKey) int page = 1; while (hasMore) { - Bundle resultBundle = searchInProgessTasks(page++); + Bundle resultBundle = searchInProgressTasks(page++); resources.add(resultBundle.getEntry().stream().filter(BundleEntryComponent::hasSearch) .filter(BundleEntryComponent::hasResource)); @@ -248,7 +248,7 @@ protected final Stream searchInProgessTasks(String businessKey) .filter(r -> r instanceof Task).map(r -> (Task) r).filter(hasBusinessKey(businessKey)); } - private Bundle searchInProgessTasks(int page) + private Bundle searchInProgressTasks(int page) { // TODO add business-key custom search parameter to FHIR server From 147efba9f6ec533c408f10b920b915258127759b Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 18:42:37 +0200 Subject: [PATCH 37/56] typo fix WARNNING -> WARNING --- .../dev/dsf/bpe/listener/DebugLoggingBpmnParseListener.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/listener/DebugLoggingBpmnParseListener.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/listener/DebugLoggingBpmnParseListener.java index 5d52b34d9..3c28168f5 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/listener/DebugLoggingBpmnParseListener.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/listener/DebugLoggingBpmnParseListener.java @@ -101,11 +101,11 @@ public void afterPropertiesSet() throws Exception { if (logVariables) logger.warn( - "Process variable debug logging enabled. This should only be activated during process plugin development. WARNNING: Confidential information may be leaked via the debug log!"); + "Process variable debug logging enabled. This should only be activated during process plugin development. WARNING: Confidential information may be leaked via the debug log!"); if (logVariablesLocal) logger.warn( - "Process local variable debug logging enabled. This should only be activated during process plugin development. WARNNING: Confidential information may be leaked via the debug log!"); + "Process local variable debug logging enabled. This should only be activated during process plugin development. WARNING: Confidential information may be leaked via the debug log!"); } else logger.warn( From ddf9c5baf24afd594dbadc3678400155c75e90e0 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 18:55:07 +0200 Subject: [PATCH 38/56] log message cleanup user -> identity --- .../authorization/AbstractAuthorizationRule.java | 9 +++++---- .../secure/AbstractResourceServiceSecure.java | 13 +++++++------ .../fhir/webservice/secure/RootServiceSecure.java | 4 ++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/AbstractAuthorizationRule.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/AbstractAuthorizationRule.java index 688942af0..5abcebe1c 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/AbstractAuthorizationRule.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/authorization/AbstractAuthorizationRule.java @@ -271,7 +271,7 @@ protected boolean isCurrentIdentityPartOfReferencedOrganization(Connection conne { if (reference == null) { - logger.warn("Null reference while checking if user part of referenced organization"); + logger.warn("Null reference while checking if identity part of referenced organization"); return false; } @@ -282,7 +282,8 @@ protected boolean isCurrentIdentityPartOfReferencedOrganization(Connection conne ReferenceType type = resReference.getType(serverBase); if (!EnumSet.of(ReferenceType.LITERAL_INTERNAL, ReferenceType.LOGICAL).contains(type)) { - logger.warn("Reference of type {} not supported while checking if user part of referenced organization", + logger.warn( + "Reference of type {} not supported while checking if identity part of referenced organization", type); return false; @@ -296,7 +297,7 @@ protected boolean isCurrentIdentityPartOfReferencedOrganization(Connection conne .equals(resource.get().getIdElement().getIdPart()); if (!sameOrganization) logger.warn( - "Current user not part of organization {} while checking if user part of referenced organization", + "Current identity not part of organization {} while checking if identity part of referenced organization", resource.get().getIdElement().getValue()); return sameOrganization; @@ -304,7 +305,7 @@ protected boolean isCurrentIdentityPartOfReferencedOrganization(Connection conne else { logger.warn( - "Reference to organization could not be resolved while checking if user part of referenced organization"); + "Reference to organization could not be resolved while checking if identity part of referenced organization"); return false; } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java index 38834f681..c4257ee62 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java @@ -188,7 +188,8 @@ public Response create(R resource, UriInfo uri, HttpHeaders headers) if (reasonCreateAllowed.isEmpty()) { - audit.info("Create of resource {} denied for user '{}'", resourceTypeName, getCurrentIdentity().getName()); + audit.info("Create of resource {} denied for identity '{}'", resourceTypeName, + getCurrentIdentity().getName()); return forbidden("create"); } else @@ -196,17 +197,17 @@ public Response create(R resource, UriInfo uri, HttpHeaders headers) return withResourceValidation(resource, validationRules::failOnErrorOrFatalBeforeCreate, uri, headers, "Create", () -> { - audit.info("Create of resource {} allowed for user '{}', reason: {}", resourceTypeName, + audit.info("Create of resource {} allowed for identity '{}', reason: {}", resourceTypeName, getCurrentIdentity().getName(), reasonCreateAllowed.get()); Response created = logResultStatus(() -> { Response response = delegate.create(resource, uri, headers); return response; - }, status -> audit.info("Create of resource {} for user '{}' successful, status: {} {}", + }, status -> audit.info("Create of resource {} for identity '{}' successful, status: {} {}", resourceTypeName, getCurrentIdentity().getName(), status.getStatusCode(), status.getReasonPhrase()), - status -> audit.info("Create of resource {} for user '{}' failed, status: {} {}", + status -> audit.info("Create of resource {} for identity '{}' failed, status: {} {}", resourceTypeName, getCurrentIdentity().getName(), status.getStatusCode(), status.getReasonPhrase())); @@ -605,7 +606,7 @@ resourceTypeName, resourceId, resourceVersion, getCurrentIdentity().getName(), } else { - audit.info("{} to delete not found for user '{}'", resourceTypeName, getCurrentIdentity().getName()); + audit.info("{} to delete not found for identity '{}'", resourceTypeName, getCurrentIdentity().getName()); return responseGenerator.notFound(id, resourceTypeName); } } @@ -738,7 +739,7 @@ resourceTypeName, resourceId, resourceVersion, getCurrentIdentity().getName(), } else { - audit.info("{} to permanently delete not found for user '{}'", resourceTypeName, + audit.info("{} to permanently delete not found for identity '{}'", resourceTypeName, getCurrentIdentity().getName()); return responseGenerator.notFound(id, resourceTypeName); } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/RootServiceSecure.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/RootServiceSecure.java index 0179ead28..5e4887cfa 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/RootServiceSecure.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/RootServiceSecure.java @@ -102,12 +102,12 @@ public Response history(UriInfo uri, HttpHeaders headers) Optional reasonHistoryAllowed = authorizationRule.reasonHistoryAllowed(getCurrentIdentity()); if (reasonHistoryAllowed.isEmpty()) { - audit.info("Root History denied for user '{}'", getCurrentIdentity().getName()); + audit.info("Root History denied for identity '{}'", getCurrentIdentity().getName()); return forbidden("history"); } else { - audit.info("Root History allowed for user '{}': {}", getCurrentIdentity().getName(), + audit.info("Root History allowed for identiy '{}': {}", getCurrentIdentity().getName(), reasonHistoryAllowed.get()); return delegate.history(uri, headers); } From 4f210051ca100805e97c775dcf02c55f7e6d2985 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 18:59:37 +0200 Subject: [PATCH 39/56] fixed log message: super class -> subclass --- .../java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java b/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java index d0b166e9b..3a9545573 100644 --- a/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java +++ b/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java @@ -243,9 +243,9 @@ public boolean isDefaultUserTaskListenerOrSuperClassOf(String className) } catch (BeansException | ClassNotFoundException | ClassCastException e) { - logger.debug("Unable check if {} is super class of {}", className, - getDefaultUserTaskListenerClass().getName(), e); - logger.warn("Unable check if {} is super class of {}: {} - {}", className, + logger.debug("Unable check if {} is subclass of {}", className, getDefaultUserTaskListenerClass().getName(), + e); + logger.warn("Unable check if {} is subclass of {}: {} - {}", className, getDefaultUserTaskListenerClass().getName(), e.getClass().getName(), e.getMessage()); throw new RuntimeException(e); From 4adac53dbe194c50ec759aafba6055117c64fcaf Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 19:01:46 +0200 Subject: [PATCH 40/56] log message cleanup 2 vs 4 digit version number --- .../java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java b/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java index 3a9545573..b27a21e61 100644 --- a/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java +++ b/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java @@ -759,8 +759,8 @@ private boolean isValid(BpmnFileAndModel fileAndModel) if (!getDefinitionResourceVersion().equals(processKeyAndVersion.getVersion())) { logger.warn( - "Operaton version tag of process in '{}' does not match process plugin version (tag: {} vs. plugin: {})", - fileAndModel.file(), processKeyAndVersion.getVersion(), getDefinitionVersion()); + "Operaton version tag of process in '{}' does not match process plugin resource version (tag: {} vs. plugin: {})", + fileAndModel.file(), processKeyAndVersion.getVersion(), getDefinitionResourceVersion()); return false; } if (!PROCESS_ID_PATTERN.matcher(processKeyAndVersion.getId()).matches()) From 038fba594bafa127188abc800ecd8f789e3b2ac9 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 19:03:28 +0200 Subject: [PATCH 41/56] typo fix hasMetadataresourceVersion -> hasMetadataResourceVersion --- .../dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java | 2 +- .../dev/dsf/bpe/api/plugin/ProcessPluginFhirConfig.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java b/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java index b27a21e61..b9d368254 100644 --- a/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java +++ b/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/AbstractProcessPlugin.java @@ -1116,7 +1116,7 @@ else if (fhirConfig.isValueSet(resource)) private boolean isValidMetadataResource(Object resource, String file) { boolean urlOk = fhirConfig.hasMetadataResourceUrl(resource); - boolean versionDefined = fhirConfig.hasMetadataresourceVersion(resource); + boolean versionDefined = fhirConfig.hasMetadataResourceVersion(resource); boolean versionOk = versionDefined && fhirConfig.getMetadataResourceVersion(resource) .map(v -> v.equals(getDefinitionResourceVersion())).orElse(false); diff --git a/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/ProcessPluginFhirConfig.java b/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/ProcessPluginFhirConfig.java index 70875d796..1f59dd97f 100644 --- a/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/ProcessPluginFhirConfig.java +++ b/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/plugin/ProcessPluginFhirConfig.java @@ -54,7 +54,7 @@ public static final record Reference(Optional system, Optional v private final Function encodeResource; private final Function> getResourceName; - private final Predicate hasMetadataresourceVersion; + private final Predicate hasMetadataResourceVersion; private final Predicate hasMetadataResourceUrl; private final Function> getMetadataResourceVersion; @@ -137,7 +137,7 @@ public ProcessPluginFhirConfig(Class resourceClass, Class activityDefiniti this.getResourceName = getResourceName; this.hasMetadataResourceUrl = hasMetadataResourceUrl; - this.hasMetadataresourceVersion = hasMetadataResourceVersion; + this.hasMetadataResourceVersion = hasMetadataResourceVersion; this.getMetadataResourceVersion = getMetadataResourceVersion; this.getActivityDefinitionUrl = getActivityDefinitionUrl; @@ -284,9 +284,9 @@ public boolean hasMetadataResourceUrl(Object metadataResource) return isMetadataResource(metadataResource) && hasMetadataResourceUrl.test(metadataResource); } - public boolean hasMetadataresourceVersion(Object metadataResource) + public boolean hasMetadataResourceVersion(Object metadataResource) { - return isMetadataResource(metadataResource) && hasMetadataresourceVersion.test(metadataResource); + return isMetadataResource(metadataResource) && hasMetadataResourceVersion.test(metadataResource); } public Optional getMetadataResourceVersion(Object metadataResource) From f57d6d82412f32b6048a74dc639c9c18585e64c4 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 19:06:17 +0200 Subject: [PATCH 42/56] log message cleanup: added missing single quote characters --- .../src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParse.java | 2 +- .../java/dev/dsf/fhir/dao/command/AuthorizationHelperImpl.java | 2 +- .../fhir/webservice/secure/AbstractResourceServiceSecure.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParse.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParse.java index 1361f1c20..3ee7e91d0 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParse.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParse.java @@ -133,7 +133,7 @@ protected TaskListener parseTaskListener(Element taskListenerElement, String tas else { TaskListener taskListener = super.parseTaskListener(taskListenerElement, taskElementId); - logger.debug("Not modifying {} in BPMN element with id '{}", taskListener.getClass().getName(), + logger.debug("Not modifying {} in BPMN element with id '{}'", taskListener.getClass().getName(), getElementId(taskListenerElement)); return taskListener; } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/AuthorizationHelperImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/AuthorizationHelperImpl.java index 6cad0c156..d617e6fdd 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/AuthorizationHelperImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/command/AuthorizationHelperImpl.java @@ -206,7 +206,7 @@ private boolean filterIncludeResource(int index, Identity identity, Resource inc }).orElseGet(() -> { logger.debug( - "Inclusion of {}/{}/_history/{} denied for identity '{} via bundle at index {}: read not allowed", + "Inclusion of {}/{}/_history/{} denied for identity '{}' via bundle at index {}: read not allowed", resourceTypeName, resourceId, resourceVersion, identity.getName(), index); return false; }); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java index c4257ee62..f73ea9297 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/AbstractResourceServiceSecure.java @@ -691,7 +691,7 @@ public Response search(UriInfo uri, HttpHeaders headers) { Response response = delegate.search(uri, headers); return response; - }, status -> audit.info("Search of {} for identity '{} successful, status: {} {}'", resourceTypeName, + }, status -> audit.info("Search of {} for identity '{}' successful, status: {} {}", resourceTypeName, getCurrentIdentity().getName(), status.getStatusCode(), status.getReasonPhrase()), status -> audit.info("Search of {} for identity '{}' failed, status: {} {}", resourceTypeName, getCurrentIdentity().getName(), status.getStatusCode(), status.getReasonPhrase())); From b418f6fd9ecf9ef80384ae7a4f9b04ffc75a8a60 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 19:08:50 +0200 Subject: [PATCH 43/56] log message level fix warn -> debug --- .../src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java | 2 +- .../main/java/dev/dsf/fhir/dao/jdbc/OrganizationDaoJdbc.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java index 9c6745930..4d10a928f 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java @@ -120,7 +120,7 @@ public Optional readActiveNotDeletedByAddress(String address) throws S } else { - logger.warn("Endpoint with url {} not found", address); + logger.debug("Endpoint with url {} not found", address); return Optional.empty(); } } diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationDaoJdbc.java index 808898ad8..4c5cfd323 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/OrganizationDaoJdbc.java @@ -151,7 +151,7 @@ public Optional readActiveNotDeletedByIdentifier(String identifier } else { - logger.warn("Organization with identifier {} not found", identifierValue); + logger.debug("Organization with identifier {} not found", identifierValue); return Optional.empty(); } } From f9f9ec423ada397c99c16a631bca887a976369ba Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 19:10:56 +0200 Subject: [PATCH 44/56] fixed wrong class name in log message MultiVersionClassDelegateTaskListener -> MultiVersionClassDelegateExecutionListener --- .../src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParse.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParse.java index 3ee7e91d0..8903adcf4 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParse.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParse.java @@ -149,7 +149,7 @@ public ExecutionListener parseExecutionListener(Element executionListenerElement List fieldDeclarations = parseFieldDeclarations(executionListenerElement); logger.debug("Modifying {} for {} in BPMN element with id '{}'", - MultiVersionClassDelegateTaskListener.class.getName(), className, + MultiVersionClassDelegateExecutionListener.class.getName(), className, getElementId(executionListenerElement)); return new MultiVersionClassDelegateExecutionListener(className, fieldDeclarations, delegateProvider); } From aed1cc228528de27ab095dcfc70cecd542ad2aef Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 19:11:30 +0200 Subject: [PATCH 45/56] typo fix corresponging -> corresponding, MIMIE -> MIME --- .../main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java index c27c04300..4f3f29716 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java @@ -321,7 +321,7 @@ public class PropertiesConfig extends AbstractCertificateAndProxyConfig implemen @Value("${dev.dsf.bpe.mail.client.certificate:#{null}}") private String mailClientCertificateFile; - @Documentation(description = "Private key corresponging to the SMTP server client certificate as PEM encoded file. Use ${env_variable}_PASSWORD* or *${env_variable}_PASSWORD_FILE* if private key is encrypted. Requires SMTP over TLS to be enabled via *DEV_DSF_BPE_MAIL_USESMTPS*", recommendation = "Use docker secret file to configure", example = "/run/secrets/smtp_server_client_certificate_private_key.pem") + @Documentation(description = "Private key corresponding to the SMTP server client certificate as PEM encoded file. Use ${env_variable}_PASSWORD* or *${env_variable}_PASSWORD_FILE* if private key is encrypted. Requires SMTP over TLS to be enabled via *DEV_DSF_BPE_MAIL_USESMTPS*", recommendation = "Use docker secret file to configure", example = "/run/secrets/smtp_server_client_certificate_private_key.pem") @Value("${dev.dsf.bpe.mail.client.certificate.private.key:#{null}}") private String mailClientCertificatePrivateKeyFile; @@ -333,7 +333,7 @@ public class PropertiesConfig extends AbstractCertificateAndProxyConfig implemen @Value("${dev.dsf.bpe.mail.smime.p12Keystore:#{null}}") private String mailSmimeSigningKeyStoreFile; - @Documentation(description = "Password to decrypt the PKCS12 encoded S/MIMIE certificate file", recommendation = "Use docker secret file to configure using *${env_variable}_FILE*", example = "/run/secrets/smime_certificate.p12.password") + @Documentation(description = "Password to decrypt the PKCS12 encoded S/MIME certificate file", recommendation = "Use docker secret file to configure using *${env_variable}_FILE*", example = "/run/secrets/smime_certificate.p12.password") @Value("${dev.dsf.bpe.mail.smime.p12Keystore.password:#{null}}") private char[] mailSmimeSigningKeyStorePassword; From 31ab95b9ec7ae10a44248970d0b6b9967b4d5330 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 19:12:22 +0200 Subject: [PATCH 46/56] fixed exception message: Organizations -> Endpoints --- .../src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java index 4d10a928f..b3ac2ae14 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/dao/jdbc/EndpointDaoJdbc.java @@ -109,7 +109,7 @@ public Optional readActiveNotDeletedByAddress(String address) throws S { logger.warn("Found multiple Endpoints with url {}", address); throw new SQLException( - "Found multiple Organizations with url " + address + ", single result expected"); + "Found multiple Endpoints with url " + address + ", single result expected"); } else { From 6e170de6092d2866898c4222bb8b16bab69144f8 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 19:13:57 +0200 Subject: [PATCH 47/56] removed incorrect "not found" end from OperationOutcome text --- .../src/main/java/dev/dsf/fhir/help/ResponseGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ResponseGenerator.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ResponseGenerator.java index 141cd97c8..144cb7500 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ResponseGenerator.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/help/ResponseGenerator.java @@ -702,7 +702,7 @@ public OperationOutcome referenceTargetMultipleMatchesLocallyByIdentifier(Intege + resourceReference.getReference().getIdentifier().getValue() + "' of reference at " + resourceReference.getLocation() + " in resource of type " + resource.getResourceType().name() + " with id " + resource.getId() - + (bundleIndex == null ? "" : " at bundle index " + bundleIndex) + " not found"); + + (bundleIndex == null ? "" : " at bundle index " + bundleIndex)); } public OperationOutcome referenceTargetNotFoundLocallyByCondition(Integer bundleIndex, Resource resource, From 6c4291ae2d0363e16de1d9cd282aa0f85b6fe0cb Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 19:14:38 +0200 Subject: [PATCH 48/56] typo fixes valus -> value, resoling -> resolving --- .../src/main/java/dev/dsf/fhir/search/SearchQuery.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java index 073f90eb3..a52107b03 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/search/SearchQuery.java @@ -321,7 +321,7 @@ private String createSortSql(List sortParameterValues) { errors.add(new SearchQueryParameterError( SearchQueryParameterErrorType.UNSUPPORTED_NUMBER_OF_VALUES, PARAMETER_SORT, null, - "More than one " + PARAMETER_SORT + " query parameter valus `" + value + "`")); + "More than one " + PARAMETER_SORT + " query parameter value `" + value + "`")); } } else @@ -532,7 +532,7 @@ public void resolveReferencesForMatching(Resource resource, DaoProvider daoProvi if (!exceptions.isEmpty()) { - SQLException sqlException = new SQLException("Error while resoling references"); + SQLException sqlException = new SQLException("Error while resolving references"); exceptions.forEach(sqlException::addSuppressed); throw sqlException; } From e314e983a8e8aa536d9ecfc4304d1edfcdd2cc35 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 19:15:29 +0200 Subject: [PATCH 49/56] typo fix sanapshotGenerator -> snapshotGenerator --- .../fhir/webservice/impl/StructureDefinitionServiceImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/StructureDefinitionServiceImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/StructureDefinitionServiceImpl.java index 7aa25a449..99d05223b 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/StructureDefinitionServiceImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/StructureDefinitionServiceImpl.java @@ -79,14 +79,14 @@ public StructureDefinitionServiceImpl(String path, String serverBase, int defaul ParameterConverter parameterConverter, ReferenceExtractor referenceExtractor, ReferenceResolver referenceResolver, ReferenceCleaner referenceCleaner, AuthorizationRuleProvider authorizationRuleProvider, StructureDefinitionDao structureDefinitionSnapshotDao, - SnapshotGenerator sanapshotGenerator, HistoryService historyService, ValidationRules validationRules) + SnapshotGenerator snapshotGenerator, HistoryService historyService, ValidationRules validationRules) { super(path, StructureDefinition.class, serverBase, defaultPageCount, dao, validator, eventHandler, exceptionHandler, eventGenerator, responseGenerator, parameterConverter, referenceExtractor, referenceResolver, referenceCleaner, authorizationRuleProvider, historyService, validationRules); this.snapshotDao = structureDefinitionSnapshotDao; - this.snapshotGenerator = sanapshotGenerator; + this.snapshotGenerator = snapshotGenerator; } @Override From 5e8a97f9631ab2cd10625921ed1259495c236fb5 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 25 Aug 2026 19:16:03 +0200 Subject: [PATCH 50/56] typo fixes generaly -> generally, individualy -> individually --- .../dev/dsf/fhir/webservice/secure/RootServiceSecure.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/RootServiceSecure.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/RootServiceSecure.java index 5e4887cfa..219fda7f9 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/RootServiceSecure.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/RootServiceSecure.java @@ -86,8 +86,8 @@ private Optional reasonHandleBundleAllowed(Bundle bundle) if (BundleType.BATCH.equals(bundle.getType()) || BundleType.TRANSACTION.equals(bundle.getType())) { logger.info( - "Handling of batch or transaction bundles generaly allowed for all, entries will be individualy evaluated"); - return Optional.of("Allowed for all, entries individualy evaluated"); + "Handling of batch or transaction bundles generally allowed for all, entries will be individually evaluated"); + return Optional.of("Allowed for all, entries individually evaluated"); } else { From e33e5bc65de4a5421296ddee3ef756d3619e8a43 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 12 Oct 2026 16:24:19 +0200 Subject: [PATCH 51/56] log message typo fix identiy -> identity, code formatting --- .../dsf/common/config/network/DynamicHostnameMatcherTest.java | 3 +-- .../java/dev/dsf/fhir/webservice/secure/RootServiceSecure.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/DynamicHostnameMatcherTest.java b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/DynamicHostnameMatcherTest.java index 8fb50b423..d8cbe851e 100644 --- a/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/DynamicHostnameMatcherTest.java +++ b/dsf-common/dsf-common-config/src/test/java/dev/dsf/common/config/network/DynamicHostnameMatcherTest.java @@ -128,9 +128,8 @@ public void recoversAfterFailedRefresh() throws Exception DynamicHostnameMatcher matcher = matcher(new MutableClock(currentTime), _ -> { if (fail.get()) - { throw new UnknownHostException("DNS failure"); - } + return new InetAddress[] { allowed }; }); diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/RootServiceSecure.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/RootServiceSecure.java index 219fda7f9..618c47111 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/RootServiceSecure.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/secure/RootServiceSecure.java @@ -107,7 +107,7 @@ public Response history(UriInfo uri, HttpHeaders headers) } else { - audit.info("Root History allowed for identiy '{}': {}", getCurrentIdentity().getName(), + audit.info("Root History allowed for identity '{}': {}", getCurrentIdentity().getName(), reasonHistoryAllowed.get()); return delegate.history(uri, headers); } From b458d812523d0ed7c57684d3de72a4f5c2a22a41 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 12 Oct 2026 18:14:04 +0200 Subject: [PATCH 52/56] JavaDoc: added class description --- .../config/network/StaticCidrMatcher.java | 17 +++++++++++++++++ .../config/network/StaticHostnameMatcher.java | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticCidrMatcher.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticCidrMatcher.java index 3cf6d2c2b..728fd56dc 100644 --- a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticCidrMatcher.java +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticCidrMatcher.java @@ -26,6 +26,23 @@ import dev.dsf.common.config.network.HostSpecParser.HostSpec; +/** + * Matches resolved socket addresses against statically configured IPv4 or IPv6 addresses and CIDR network ranges, with + * optional port matching. + *

+ * Supported host specifications include individual IPv4 and IPv6 addresses, which are treated as full-length network + * prefixes, and IPv4 and IPv6 CIDR ranges, which match addresses sharing the specified network prefix. + *

+ * Matching compares the binary address representations and requires the candidate address and configured network to use + * the same address family. IPv4 and IPv6 addresses therefore never match each other. CIDR matching compares all + * complete prefix bytes and, when necessary, the remaining prefix bits. + *

+ * A {@link HostSpec} may specify a port to restrict matches to that port. If no port is specified, any port is + * accepted. + *

+ * A {@code null} or unresolved socket address never matches. Matching uses the resolved binary address and does not + * perform hostname-based matching. + */ public class StaticCidrMatcher implements InetSocketAddressMatcher { public static InetSocketAddressMatcherList of(HostSpec... hostSpecs) diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticHostnameMatcher.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticHostnameMatcher.java index d33d987da..b3954b150 100644 --- a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticHostnameMatcher.java +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticHostnameMatcher.java @@ -23,6 +23,24 @@ import dev.dsf.common.config.network.HostSpecParser.HostSpec; +/** + * Matches socket addresses against a statically configured hostname pattern with optional port matching. + *

+ * Supported hostname patterns are: + *

    + *
  • Exact hostnames, which match the specified hostname only.
  • + *
  • Single-level wildcards ({@code *.example.invalid}), which match exactly one subdomain level.
  • + *
  • Multi-level wildcards ({@code .example.invalid}), which match one or more subdomain levels.
  • + *
+ * Hostname comparisons are case-insensitive. Wildcard patterns require the candidate hostname to end with the specified + * suffix, preceded by a dot. The suffix itself does not match a wildcard pattern. + *

+ * A {@link HostSpec} may specify a port to restrict matches to that port. If no port is specified, any port is + * accepted. + *

+ * A {@code null} socket address never matches. The original hostname representation is used for matching without + * requiring a reverse DNS lookup. + */ public final class StaticHostnameMatcher implements InetSocketAddressMatcher { public static InetSocketAddressMatcherList of(HostSpec... hostSpecs) From b6d060e47005ec6ad1648098ec411e31abd22ddb Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 12 Oct 2026 18:45:55 +0200 Subject: [PATCH 53/56] simplified trusted proxy config code, removed not used function - Remove never used JettyServer#httpConnector(ServerSocketChannel, String, Duration, List) function. - Simplified trusted proxy matcher config code and fixed an inconsistent HostSpec type check (isDomain vs. isDomainOrWildcard). The DynamicHostnameMatcher used for the trusted proxy config only supports non wildcard domains (isDomain). Configured wildcard domains would have been completely ignored. --- .../bpe/spring/config/PropertiesConfig.java | 2 +- .../AbstractCertificateAndProxyConfig.java | 5 +- .../common/config/AbstractJettyConfig.java | 39 +++++++++- .../ForwardedSecureRequestCustomizer.java | 8 +- .../dev/dsf/common/jetty/JettyServer.java | 75 +------------------ .../fhir/spring/config/PropertiesConfig.java | 2 +- 6 files changed, 48 insertions(+), 83 deletions(-) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java index 4f3f29716..2336cb024 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java @@ -994,7 +994,7 @@ public int getJettyStatusConnectorPort() @Bean public ProxyConfig proxyConfig() { - List proxyNoProxy = parseHostSpecList("dev.dsf.proxy.noProxy", this.proxyNoProxy, false); + List proxyNoProxy = parseHostSpecList("dev.dsf.proxy.noProxy", this.proxyNoProxy); return new ProxyConfigImpl(proxyUrl, proxyUsername, proxyPassword, proxyNoProxy); } diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/AbstractCertificateAndProxyConfig.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/AbstractCertificateAndProxyConfig.java index 3360519ac..d569cd11d 100644 --- a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/AbstractCertificateAndProxyConfig.java +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/AbstractCertificateAndProxyConfig.java @@ -325,11 +325,8 @@ protected KeyStore createKeyStoreFromP12(String file, char[] keyStorePassword, S } } - protected List parseHostSpecList(String propertyName, List values, boolean supportDisabled) + protected List parseHostSpecList(String propertyName, List values) { - if (supportDisabled && values != null && values.size() == 1 && HOST_SPEC_LIST_DISABLED.equals(values.get(0))) - return List.of(); - try { return HostSpecParser.parse(values); diff --git a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java index 0402bc78b..9a11184dd 100644 --- a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java +++ b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java @@ -82,7 +82,11 @@ import dev.dsf.common.auth.StatusPortAuthenticator; import dev.dsf.common.buildinfo.BuildInfoReader; import dev.dsf.common.buildinfo.BuildInfoReaderImpl; +import dev.dsf.common.config.network.DynamicHostnameMatcher; import dev.dsf.common.config.network.HostSpecParser.HostSpec; +import dev.dsf.common.config.network.InetSocketAddressMatcher; +import dev.dsf.common.config.network.InetSocketAddressMatcherList; +import dev.dsf.common.config.network.StaticCidrMatcher; import dev.dsf.common.docker.secrets.DockerSecretsPropertySourceFactory; import dev.dsf.common.documentation.Documentation; import dev.dsf.common.jetty.HttpClientWithGetRetry; @@ -253,12 +257,39 @@ public abstract class AbstractJettyConfig extends AbstractCertificateAndProxyCon protected final Function httpApiConnector() { + return JettyServer.httpConnector(apiHost, apiPort, clientCertificateHeaderName, createTrustedProxyMatcher()); + } + + private InetSocketAddressMatcher createTrustedProxyMatcher() + { + if (trustedReverseProxies.size() == 1 && HOST_SPEC_LIST_DISABLED.equals(trustedReverseProxies.get(0))) + { + logger.warn("Trusted reverse proxy matcher disabled"); + return InetSocketAddressMatcher.ALL; + } + Duration trustedReverseProxiesDnsRefreshTimeout = Duration.parse(trustedReverseProxiesRefreshTimeout); List trustedReverseProxies = parseHostSpecList("dev.dsf.server.auth.trust.reverse.proxy", - this.trustedReverseProxies, true); + this.trustedReverseProxies); - return JettyServer.httpConnector(apiHost, apiPort, clientCertificateHeaderName, - trustedReverseProxiesDnsRefreshTimeout, trustedReverseProxies); + InetSocketAddressMatcher matcher = new InetSocketAddressMatcherList( + trustedReverseProxies.stream().map(toMatcher(trustedReverseProxiesDnsRefreshTimeout))); + + logger.info("Trusted reverse proxy matcher config: {}", matcher.toString()); + return matcher; + } + + private Function toMatcher(Duration trustedReverseProxiesDnsRefreshTimeout) + { + return hostSpec -> + { + if (hostSpec.isIp()) + return StaticCidrMatcher.of(hostSpec); + else if (hostSpec.isDomain()) + return DynamicHostnameMatcher.of(trustedReverseProxiesDnsRefreshTimeout, hostSpec); + else + throw new IllegalArgumentException("hostSpec not supported"); + }; } protected final Function httpsApiConnector() @@ -521,7 +552,7 @@ private Proxy oidcClientProxy() @Lazy public ProxyConfig proxyConfig() { - List proxyNoProxy = parseHostSpecList("dev.dsf.proxy.noProxy", this.proxyNoProxy, false); + List proxyNoProxy = parseHostSpecList("dev.dsf.proxy.noProxy", this.proxyNoProxy); return new ProxyConfigImpl(proxyUrl, proxyUsername, proxyPassword, proxyNoProxy); } diff --git a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/ForwardedSecureRequestCustomizer.java b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/ForwardedSecureRequestCustomizer.java index d325f635b..4e213b2d1 100644 --- a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/ForwardedSecureRequestCustomizer.java +++ b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/ForwardedSecureRequestCustomizer.java @@ -45,10 +45,16 @@ public class ForwardedSecureRequestCustomizer implements Customizer private final String clientCertHeaderName; private final InetSocketAddressMatcher trustedProxyMatcher; + /** + * @param clientCertHeaderName + * not null + * @param trustedProxyMatcher + * not null + */ public ForwardedSecureRequestCustomizer(String clientCertHeaderName, InetSocketAddressMatcher trustedProxyMatcher) { this.clientCertHeaderName = Objects.requireNonNull(clientCertHeaderName, "clientCertHeaderName"); - this.trustedProxyMatcher = trustedProxyMatcher; + this.trustedProxyMatcher = Objects.requireNonNull(trustedProxyMatcher, "trustedProxyMatcher"); } @Override diff --git a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/JettyServer.java b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/JettyServer.java index 9da776c0b..08e3375ad 100644 --- a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/JettyServer.java +++ b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/jetty/JettyServer.java @@ -21,7 +21,6 @@ import java.net.StandardSocketOptions; import java.nio.channels.ServerSocketChannel; import java.security.KeyStore; -import java.time.Duration; import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -57,11 +56,7 @@ import de.hsheilbronn.mi.utils.crypto.cert.CertificateFormatter.X500PrincipalFormat; import de.hsheilbronn.mi.utils.crypto.keystore.KeyStoreFormatter; -import dev.dsf.common.config.network.DynamicHostnameMatcher; -import dev.dsf.common.config.network.HostSpecParser.HostSpec; import dev.dsf.common.config.network.InetSocketAddressMatcher; -import dev.dsf.common.config.network.InetSocketAddressMatcherList; -import dev.dsf.common.config.network.StaticCidrMatcher; import jakarta.servlet.ServletContainerInitializer; import jakarta.servlet.ServletContext; @@ -146,15 +141,13 @@ public static Function statusConnector(ServerSocketChan } public static Function httpConnector(String host, int port, - String clientCertificateHeaderName, Duration trustedReverseProxiesDnsRefreshTimeout, - List trustedReverseProxies) + String clientCertificateHeaderName, InetSocketAddressMatcher trustedProxyMatcher) { return server -> { ServerConnector connector = new ServerConnector(server, - httpConnectionFactory(new ForwardedRequestCustomizer(), new ForwardedSecureRequestCustomizer( - clientCertificateHeaderName, - createTrustedProxyMatcher(trustedReverseProxiesDnsRefreshTimeout, trustedReverseProxies)))); + httpConnectionFactory(new ForwardedRequestCustomizer(), + new ForwardedSecureRequestCustomizer(clientCertificateHeaderName, trustedProxyMatcher))); connector.setHost(host); connector.setPort(port); @@ -162,68 +155,6 @@ public static Function httpConnector(String host, int p }; } - public static Function httpConnector(ServerSocketChannel channel, - String clientCertificateHeaderName, Duration trustedReverseProxiesDnsRefreshTimeout, - List trustedReverseProxies) - { - return server -> - { - try - { - ServerConnector connector = new ServerConnector(server, - httpConnectionFactory(new ForwardedRequestCustomizer(), - new ForwardedSecureRequestCustomizer(clientCertificateHeaderName, - createTrustedProxyMatcher(trustedReverseProxiesDnsRefreshTimeout, - trustedReverseProxies)))); - connector.open(channel); - setHostAndPort(channel, connector); - - return connector; - } - catch (IOException e) - { - logger.debug("Unable to open server socket channel", e); - logger.warn("Unable to open server socket channel: {} - {}", e.getClass().getName(), e.getMessage()); - - throw new RuntimeException(e); - } - }; - } - - private static InetSocketAddressMatcher createTrustedProxyMatcher(Duration trustedReverseProxiesDnsRefreshTimeout, - List trustedReverseProxies) - { - if (trustedReverseProxies == null) - { - logger.warn("Trusted reverse proxy matcher disabled"); - - return InetSocketAddressMatcher.ALL; - } - else - { - InetSocketAddressMatcher matcher = new InetSocketAddressMatcherList( - trustedReverseProxies.stream().map(toMatcher(trustedReverseProxiesDnsRefreshTimeout))); - - logger.info("Trusted reverse proxy matcher config: {}", matcher.toString()); - - return matcher; - } - } - - private static Function toMatcher( - Duration trustedReverseProxiesDnsRefreshTimeout) - { - return hostSpec -> - { - if (hostSpec.isIp()) - return StaticCidrMatcher.of(hostSpec); - else if (hostSpec.isDomainOrWildcard()) - return DynamicHostnameMatcher.of(trustedReverseProxiesDnsRefreshTimeout, hostSpec); - else - throw new IllegalArgumentException("hostSpec not supported"); - }; - } - public static Function httpsConnector(ServerSocketChannel channel, KeyStore clientCertificateTrustStore, KeyStore serverCertificateKeyStore, char[] keyStorePassword, boolean needClientAuth) diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java index 7b3e86a48..c0a52ca96 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java @@ -421,7 +421,7 @@ public boolean getOidcBearerTokenEnabled() @Bean public ProxyConfig proxyConfig() { - List proxyNoProxy = parseHostSpecList("dev.dsf.proxy.noProxy", this.proxyNoProxy, false); + List proxyNoProxy = parseHostSpecList("dev.dsf.proxy.noProxy", this.proxyNoProxy); return new ProxyConfigImpl(proxyUrl, proxyUsername, proxyPassword, proxyNoProxy); } From c844d39da9773a8659a0887a1640940293604e17 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 12 Oct 2026 19:40:29 +0200 Subject: [PATCH 54/56] removed special `*` case from JavaDoc --- .../src/main/java/dev/dsf/bpe/v1/config/ProxyConfig.java | 2 +- .../src/main/java/dev/dsf/bpe/v2/config/ProxyConfig.java | 8 ++++---- .../src/main/java/dev/dsf/common/config/ProxyConfig.java | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dsf-bpe/dsf-bpe-process-api-v1-base/src/main/java/dev/dsf/bpe/v1/config/ProxyConfig.java b/dsf-bpe/dsf-bpe-process-api-v1-base/src/main/java/dev/dsf/bpe/v1/config/ProxyConfig.java index b468011f0..af1002948 100644 --- a/dsf-bpe/dsf-bpe-process-api-v1-base/src/main/java/dev/dsf/bpe/v1/config/ProxyConfig.java +++ b/dsf-bpe/dsf-bpe-process-api-v1-base/src/main/java/dev/dsf/bpe/v1/config/ProxyConfig.java @@ -25,7 +25,7 @@ public interface ProxyConfig String getUrl(); /** - * @return true if a proxy url is configured and '*' is not set as a no-proxy url + * @return true if a proxy url is configured */ boolean isEnabled(); diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/config/ProxyConfig.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/config/ProxyConfig.java index e9801b46b..d988a9d27 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/config/ProxyConfig.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/config/ProxyConfig.java @@ -25,16 +25,16 @@ public interface ProxyConfig String getUrl(); /** - * @return true if a proxy url is configured and '*' is not set as a no-proxy url + * @return true if a proxy url is configured */ boolean isEnabled(); /** * @param targetUrl * may be null - * @return true if a proxy url is configured, '*' is not set as a no-proxy url and the given - * targetUrl is not set as a no-proxy url, false if the given targetUrl is - * null, blank or configured as a no-proxy url + * @return true if a proxy url is configured, and the given targetUrl is not set as a no-proxy + * url, false if the given targetUrl is null, blank or configured as a + * no-proxy url * @see #getNoProxyUrls() * @see String#isBlank() */ diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/ProxyConfig.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/ProxyConfig.java index dd906f67a..a58f13bb7 100644 --- a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/ProxyConfig.java +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/ProxyConfig.java @@ -25,16 +25,16 @@ public interface ProxyConfig String getUrl(); /** - * @return true if a proxy url is configured and '*' is not set as a no-proxy url + * @return true if a proxy url is configured */ boolean isEnabled(); /** * @param targetUrl * may be null - * @return true if a proxy url is configured, '*' is not set as a no-proxy url and the given - * targetUrl is not set as a no-proxy url, false if the given targetUrl is - * null, blank or configured as a no-proxy url + * @return true if a proxy url is configured, and the given targetUrl is not set as a no-proxy + * url, false if the given targetUrl is null, blank or configured as a + * no-proxy url * @see #getNoProxyUrls() * @see String#isBlank() */ From 55cc0c73a412e723d0a57f68a4508ec69b770e0e Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 12 Oct 2026 20:13:49 +0200 Subject: [PATCH 55/56] added comment about Java networkaddress.cache security properties to doc --- .../main/java/dev/dsf/common/config/AbstractJettyConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java index 9a11184dd..7fae8b6a5 100644 --- a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java +++ b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java @@ -137,7 +137,7 @@ public abstract class AbstractJettyConfig extends AbstractCertificateAndProxyCon @Value("#{'${dev.dsf.server.auth.trust.reverse.proxy:proxy}'.trim().split('[,\\s]+')}") private List trustedReverseProxies; - @Documentation(description = "Refresh timeout after which a trusted reverse proxy hostname is re-resolved", recommendation = "The refresh timeout should be chosen according to the acceptable stale-authorization window and e.g. the expected reverse-proxy replacement time") + @Documentation(description = "Refresh timeout after which a trusted reverse proxy hostname is re-resolved", recommendation = "The refresh timeout should be chosen according to the acceptable stale-authorization window and e.g. the expected reverse-proxy replacement time; users may also need to change the Java [networkaddress.cache...](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/net/doc-files/net-properties.html#address-cache-heading) security properties") @Value("${dev.dsf.server.auth.trust.reverse.proxy.hostname.refresh.timeout:PT10S}") private String trustedReverseProxiesRefreshTimeout; From 6e854e0a7b3a713642727ba284fc3e3fe31c853a Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Mon, 12 Oct 2026 20:47:00 +0200 Subject: [PATCH 56/56] added brackets to toString value of IPV6 addresses --- .../dev/dsf/common/config/network/StaticCidrMatcher.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticCidrMatcher.java b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticCidrMatcher.java index 728fd56dc..45a9af8f7 100644 --- a/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticCidrMatcher.java +++ b/dsf-common/dsf-common-config/src/main/java/dev/dsf/common/config/network/StaticCidrMatcher.java @@ -131,7 +131,10 @@ public String toString() { try { - return InetAddress.getByAddress(network).getHostAddress() + "/" + prefixLength + InetAddress address = InetAddress.getByAddress(network); + boolean isIpv6 = address.getAddress().length == 16; + + return (isIpv6 ? "[" : "") + address.getHostAddress() + "/" + prefixLength + (isIpv6 ? "]" : "") + (port == null ? "" : ":" + port); } catch (UnknownHostException e)