diff --git a/dsf-bpe/dsf-bpe-server-jetty/src/main/java/dev/dsf/bpe/config/BpeDbMigratorConfig.java b/dsf-bpe/dsf-bpe-server-jetty/src/main/java/dev/dsf/bpe/config/BpeDbMigratorConfig.java
index bb64598c2..f59b523a7 100644
--- a/dsf-bpe/dsf-bpe-server-jetty/src/main/java/dev/dsf/bpe/config/BpeDbMigratorConfig.java
+++ b/dsf-bpe/dsf-bpe-server-jetty/src/main/java/dev/dsf/bpe/config/BpeDbMigratorConfig.java
@@ -17,6 +17,7 @@
import java.util.Map;
+import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -32,7 +33,7 @@
@Configuration
@PropertySource(value = "file:conf/config.properties", encoding = "UTF-8", ignoreResourceNotFound = true)
-public class BpeDbMigratorConfig implements DbMigratorConfig
+public class BpeDbMigratorConfig implements DbMigratorConfig, InitializingBean
{
private static final String DB_LIQUIBASE_USER = "db.liquibase_user";
private static final String DB_SERVER_USERS_GROUP = "db.server_users_group";
@@ -97,6 +98,26 @@ public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderCon
return new PropertySourcesPlaceholderConfigurer();
}
+ @Override
+ public void afterPropertiesSet() throws Exception
+ {
+ if (!POSTGRES_UNQUOTED_IDENTIFIER.matcher(dbLiquibaseUsername).matches())
+ throw new RuntimeException("Property 'dev.dsf.bpe.db.liquibase.username' value not matching "
+ + POSTGRES_UNQUOTED_IDENTIFIER_STRING);
+ if (!POSTGRES_UNQUOTED_IDENTIFIER.matcher(dbUsersGroup).matches())
+ throw new RuntimeException(
+ "Property 'dev.dsf.bpe.db.user.group' value not matching " + POSTGRES_UNQUOTED_IDENTIFIER_STRING);
+ if (!POSTGRES_UNQUOTED_IDENTIFIER.matcher(dbUsername).matches())
+ throw new RuntimeException("Property 'dev.dsf.bpe.db.user.username' value not matching "
+ + POSTGRES_UNQUOTED_IDENTIFIER_STRING);
+ if (!POSTGRES_UNQUOTED_IDENTIFIER.matcher(dbEngineUsersGroup).matches())
+ throw new RuntimeException("Property 'dev.dsf.bpe.db.user.engine.group' value not matching "
+ + POSTGRES_UNQUOTED_IDENTIFIER_STRING);
+ if (!POSTGRES_UNQUOTED_IDENTIFIER.matcher(dbEngineUsername).matches())
+ throw new RuntimeException("Property 'dev.dsf.bpe.db.user.engine.username' value not matching "
+ + POSTGRES_UNQUOTED_IDENTIFIER_STRING);
+ }
+
@Override
public String getDbUrl()
{
diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/authentication/IdentityProviderImpl.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/authentication/IdentityProviderImpl.java
index 845c936ea..6ec39be4a 100644
--- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/authentication/IdentityProviderImpl.java
+++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/authentication/IdentityProviderImpl.java
@@ -80,14 +80,14 @@ public Identity getIdentity(X509Certificate[] certificates)
Organization o = localOrganization.get();
Endpoint e = localEndpoint.get();
- return new PractitionerIdentityImpl(o, e, getDsfRolesFor(p, certWrapper.thumbprint(), null, null),
- certWrapper, p, getPractitionerRolesFor(p, certWrapper.thumbprint(), null, null), null);
+ return new PractitionerIdentityImpl(o, e, getDsfRolesFor(p, certWrapper.getThumbprint(), null, null),
+ certWrapper, p, getPractitionerRolesFor(p, certWrapper.getThumbprint(), null, null), null);
}
else
{
logger.warn(
"Certificate with thumbprint '{}' for '{}' unknown, not configured as local user or local organization unknown",
- certWrapper.thumbprint(), certWrapper.subjectDn());
+ certWrapper.getThumbprint(), certWrapper.getSubjectDn());
return null;
}
}
diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/oidc/OidcClientJersey.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/oidc/OidcClientJersey.java
index fe4aa9924..513d8597e 100644
--- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/oidc/OidcClientJersey.java
+++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/oidc/OidcClientJersey.java
@@ -144,7 +144,12 @@ public DecodedJWT getAccessTokenDecoded(OidcConfiguration configuration, Jwks jw
"OIDC provider does not support Client Credentials Grant, supported grant types: "
+ configuration.grantTypesSupported());
- Response response = client.target(configuration.tokenEndpoint()).request(MediaType.APPLICATION_JSON_TYPE)
+ String tokenEndpoint = configuration.tokenEndpoint();
+ if (tokenEndpoint == null || !tokenEndpoint.startsWith("https://"))
+ throw new OidcClientException(
+ "Token endpoint URL from OIDC configuration resource is null or does not start with 'https://'");
+
+ Response response = client.target(tokenEndpoint).request(MediaType.APPLICATION_JSON_TYPE)
.header(HttpHeaders.AUTHORIZATION,
"Basic " + Base64.getEncoder()
.encodeToString(new StringBuilder().append(clientId).append(':').append(clientSecret)
@@ -164,22 +169,6 @@ public DecodedJWT getAccessTokenDecoded(OidcConfiguration configuration, Jwks jw
}
}
- /**
- * Does not verify if the access token is expired. Supported algorithms: RS256, RS384, RS512, ES256, ES384 and
- * ES512.
- *
- * @param accessToken
- * not null
- * @param jwks
- * not null
- * @return decoded access token
- * @throws OidcClientException
- * if verification fails, the public key to verify is unknown or a unsupported signature algorithm was
- * used.
- *
- * @see DecodedJWT#getExpiresAt()
- * @see DecodedJWT#getExpiresAtAsInstant()
- */
private DecodedJWT verifyAndDecodeAccessToken(String accessToken, Jwks jwks) throws OidcClientException
{
try
@@ -191,14 +180,15 @@ private DecodedJWT verifyAndDecodeAccessToken(String accessToken, Jwks jwks) thr
throw new OidcClientException("Access token has no kid property");
Optional key = jwks.getKey(keyId);
- if (key.isEmpty())
- throw new OidcClientException("Access token key with kid '" + keyId + "' not in JWKS");
+ if (key.isEmpty() || !key.get().use().equals("sig"))
+ throw new OidcClientException("Access token key with kid '" + keyId + "' and use 'sig' not in JWKS");
- Optional algorithm = key.map(JwksKey::toAlgorithm);
+ Optional algorithm = key.flatMap(JwksKey::toAlgorithm);
if (key.isEmpty())
+ {
throw new OidcClientException("Access token key with kid '" + keyId
- + "' has unsupported type (kty) / algorithm (alg) in JWKS '" + key.get().kty() + "' / '"
- + key.get().alg() + "'");
+ + "' has unsupported type (kty) / algorithm (alg) / key-size in JWKS");
+ }
try
{
@@ -226,7 +216,7 @@ else if (requiredAudiences.size() > 1)
}
catch (TokenExpiredException e)
{
- throw new OidcClientException("JWT verification failed: claim missing", e);
+ throw new OidcClientException("JWT verification failed: token expired", e);
}
catch (IncorrectClaimException e)
{
diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/oidc/OidcClientWithCache.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/oidc/OidcClientWithCache.java
index 8dac3292a..edb3a61f8 100644
--- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/oidc/OidcClientWithCache.java
+++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/client/oidc/OidcClientWithCache.java
@@ -32,7 +32,7 @@ private static final record CacheEntry(ZonedDateTime timeout, T resource)
{
}
- private final Duration cacheTimeoutconfigurationResource;
+ private final Duration cacheTimeoutConfigurationResource;
private final Duration cacheTimeoutJwksResource;
private final Duration cacheTimeoutAccessTokenBeforeExpiration;
private final OidcClientWithDecodedJwt delegate;
@@ -42,7 +42,7 @@ private static final record CacheEntry(ZonedDateTime timeout, T resource)
private CacheEntry accessTokenCache;
/**
- * @param cacheTimeoutconfigurationResource
+ * @param cacheTimeoutConfigurationResource
* not null, not negative
* @param cacheTimeoutJwksResource
* not null, not negative
@@ -51,13 +51,13 @@ private static final record CacheEntry(ZonedDateTime timeout, T resource)
* @param delegate
* not null
*/
- public OidcClientWithCache(Duration cacheTimeoutconfigurationResource, Duration cacheTimeoutJwksResource,
+ public OidcClientWithCache(Duration cacheTimeoutConfigurationResource, Duration cacheTimeoutJwksResource,
Duration cacheTimeoutAccessTokenBeforeExpiration, OidcClientWithDecodedJwt delegate)
{
- this.cacheTimeoutconfigurationResource = Objects.requireNonNull(cacheTimeoutconfigurationResource,
- "cacheTimeoutconfigurationResource");
- if (cacheTimeoutconfigurationResource.isNegative())
- throw new IllegalArgumentException("cacheTimeoutconfigurationResource negative");
+ this.cacheTimeoutConfigurationResource = Objects.requireNonNull(cacheTimeoutConfigurationResource,
+ "cacheTimeoutConfigurationResource");
+ if (cacheTimeoutConfigurationResource.isNegative())
+ throw new IllegalArgumentException("cacheTimeoutConfigurationResource negative");
this.cacheTimeoutJwksResource = Objects.requireNonNull(cacheTimeoutJwksResource, "cacheTimeoutJwksResource");
if (cacheTimeoutJwksResource.isNegative())
@@ -73,13 +73,13 @@ public OidcClientWithCache(Duration cacheTimeoutconfigurationResource, Duration
@Override
public Configuration getConfiguration() throws OidcClientException
{
- if (configurationCache != null && configurationCache.timeout.isBefore(ZonedDateTime.now()))
+ if (configurationCache != null && configurationCache.timeout.isAfter(ZonedDateTime.now()))
return configurationCache.resource;
else
{
Configuration configuration = delegate.getConfiguration();
configurationCache = new CacheEntry(
- ZonedDateTime.now().plus(cacheTimeoutconfigurationResource), configuration);
+ ZonedDateTime.now().plus(cacheTimeoutConfigurationResource), configuration);
return configuration;
}
}
@@ -89,7 +89,7 @@ public Jwks getJwks() throws OidcClientException
{
Configuration configuration = getConfiguration();
- if (jwksCache != null && jwksCache.timeout.isBefore(ZonedDateTime.now()))
+ if (jwksCache != null && jwksCache.timeout.isAfter(ZonedDateTime.now()))
return jwksCache.resource;
else
{
@@ -115,7 +115,7 @@ public DecodedJWT getAccessTokenDecoded() throws OidcClientException
@Override
public DecodedJWT getAccessTokenDecoded(Configuration configuration, Jwks jwks) throws OidcClientException
{
- if (accessTokenCache != null && accessTokenCache.timeout.isBefore(ZonedDateTime.now()))
+ if (accessTokenCache != null && accessTokenCache.timeout.isAfter(ZonedDateTime.now()))
return accessTokenCache.resource;
else
{
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 b0cf8d986..9965e151f 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.AbstractCertificateConfig;
import dev.dsf.common.config.ProxyConfig;
import dev.dsf.common.config.ProxyConfigImpl;
+import dev.dsf.common.db.migration.DbMigratorConfig;
import dev.dsf.common.docker.secrets.DockerSecretsPropertySourceFactory;
import dev.dsf.common.documentation.Documentation;
import dev.dsf.common.ui.theme.Theme;
@@ -451,6 +452,13 @@ else if (oldPropertyValue != null && newPropertyValue == null)
@Override
public void afterPropertiesSet() throws Exception
{
+ if (!DbMigratorConfig.POSTGRES_UNQUOTED_IDENTIFIER.matcher(dbUsername).matches())
+ throw new RuntimeException("Property 'dev.dsf.bpe.db.user.username' value not matching "
+ + DbMigratorConfig.POSTGRES_UNQUOTED_IDENTIFIER_STRING);
+ if (!DbMigratorConfig.POSTGRES_UNQUOTED_IDENTIFIER.matcher(dbEngineUsername).matches())
+ throw new RuntimeException("Property 'dev.dsf.bpe.db.user.engine.username' value not matching "
+ + DbMigratorConfig.POSTGRES_UNQUOTED_IDENTIFIER_STRING);
+
URL url = new URI(dsfServerBaseUrl).toURL();
if (!List.of("http", "https").contains(url.getProtocol()))
{
diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/webservice/ProcessService.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/webservice/ProcessService.java
index 210ef364f..940826777 100755
--- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/webservice/ProcessService.java
+++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/webservice/ProcessService.java
@@ -23,7 +23,9 @@
import java.util.Objects;
import java.util.function.Consumer;
+import javax.xml.XMLConstants;
import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
@@ -46,6 +48,7 @@
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.core.StreamingOutput;
+import net.sf.saxon.lib.FeatureKeys;
@RolesAllowed("ADMIN")
@Path(ProcessService.PATH)
@@ -61,7 +64,20 @@ public ProcessService(ThymeleafTemplateService templateService, RepositoryServic
super(templateService, "Process");
this.repositoryService = repositoryService;
+
transformerFactory = TransformerFactory.newInstance();
+ transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
+ transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
+
+ try
+ {
+ transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ transformerFactory.setFeature(FeatureKeys.ALLOW_EXTERNAL_FUNCTIONS, false);
+ }
+ catch (TransformerConfigurationException e)
+ {
+ throw new RuntimeException(e);
+ }
}
@Override
diff --git a/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/template/main.html b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/template/main.html
index 3519df25d..60fd3f9ed 100644
--- a/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/template/main.html
+++ b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/template/main.html
@@ -42,7 +42,7 @@
-
+