Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,7 @@ public void execute(DelegateExecution execution) throws Exception
// do not stop process execution
catch (ErrorBoundaryEvent event)
{
MessageSendTaskErrorHandler handler = delegate.getErrorHandler();
if (handler != null)
event = handler.handleErrorBoundaryEvent(api, variables, event);

if (event != null)
throw new BpmnError(event.getErrorCode(), event.getErrorMessage(), event);
// else, do nothing if event was absorbed by error handler
handleErrorBoundaryEvent(variables, event);
}
// stop process execution if exception not absorbed by error handler
catch (Exception exception)
Expand All @@ -62,10 +56,25 @@ public void execute(DelegateExecution execution) throws Exception
if (handler != null)
exception = handler.handleException(api, variables, sendTaskValues, exception);

if (exception != null)
// do not stop process execution if exception translated to error boundary event
if (exception instanceof ErrorBoundaryEvent event)
handleErrorBoundaryEvent(variables, event);

else if (exception != null)
execution.getProcessEngine().getRuntimeService().deleteProcessInstance(execution.getProcessInstanceId(),
exception.getMessage());
// else, do nothing if exception was absorbed by error handler
}
}

private void handleErrorBoundaryEvent(Variables variables, ErrorBoundaryEvent event)
{
MessageSendTaskErrorHandler handler = delegate.getErrorHandler();
if (handler != null)
event = handler.handleErrorBoundaryEvent(api, variables, event);

// do nothing if event was absorbed by error handler
if (event != null)
throw new BpmnError(event.getErrorCode(), event.getErrorMessage(), event);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,33 @@

public class JsonHolder
{
private final String dataClassName;
private final byte[] data;

/**
* @param dataClassName
* not <code>null</code>
* @param data
* not <code>null</code>
* @return
*/
public JsonHolder(String dataClassName, byte[] data)
public static JsonHolder of(String dataClassName, byte[] data)
{
Objects.requireNonNull(dataClassName, "dataClassName");
Objects.requireNonNull(data, "data");

return new JsonHolder(dataClassName, data);
}

public static JsonHolder empty()
{
return new JsonHolder(null, null);
}

private final String dataClassName;
private final byte[] data;

private JsonHolder(String dataClassName, byte[] data)
{
this.dataClassName = Objects.requireNonNull(dataClassName, "dataClassName");
this.data = Objects.requireNonNull(data, "data");
this.dataClassName = dataClassName;
this.data = data;
}

public String getDataClassName()
Expand All @@ -43,4 +57,9 @@ public byte[] getData()
{
return data;
}

public boolean isEmpty()
{
return dataClassName == null || data == null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ public JsonHolderValue readValue(ValueFields valueFields, boolean asTransientVal
String dataClassName = valueFields.getTextValue();
byte[] data = valueFields.getByteArrayValue();

return JsonHolderValues.create(new JsonHolder(dataClassName, data));
return JsonHolderValues.create(
dataClassName == null || data == null ? JsonHolder.empty() : JsonHolder.of(dataClassName, data));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,15 @@ public VariablesImpl(DelegateExecution execution, ObjectMapper objectMapper, Dsf

private JsonHolder toJsonHolder(Object json)
{
if (json == null)
return JsonHolder.empty();

try
{
byte[] data = objectMapper.writeValueAsBytes(json);
String dataClassName = json.getClass().getName();

return new JsonHolder(dataClassName, data);
return JsonHolder.of(dataClassName, data);
}
catch (JsonProcessingException e)
{
Expand All @@ -123,11 +126,15 @@ private JsonHolder toJsonHolder(Object json)
@SuppressWarnings("unchecked")
private <T> T fromJsonHolder(JsonHolder holder)
{
if (holder.isEmpty())
return null;

try
{
byte[] data = holder.getData();
Class<?> dataClass = getClassLoader().loadClass(holder.getDataClassName());
String dataClassName = holder.getDataClassName();

Class<?> dataClass = getClassLoader().loadClass(dataClassName);
return (T) objectMapper.readValue(data, dataClass);
}
catch (ClassNotFoundException | IOException e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package dev.dsf.bpe.client.oidc;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.time.Duration;
Expand Down Expand Up @@ -152,8 +153,10 @@ public DecodedJWT getAccessTokenDecoded(OidcConfiguration configuration, Jwks jw
Response response = client.target(tokenEndpoint).request(MediaType.APPLICATION_JSON_TYPE)
.header(HttpHeaders.AUTHORIZATION,
"Basic " + Base64.getEncoder()
.encodeToString(new StringBuilder().append(clientId).append(':').append(clientSecret)
.toString().getBytes(StandardCharsets.US_ASCII)))
.encodeToString(new StringBuilder()
.append(URLEncoder.encode(clientId, StandardCharsets.UTF_8)).append(':')
.append(URLEncoder.encode(String.valueOf(clientSecret), StandardCharsets.UTF_8))
.toString().getBytes(StandardCharsets.UTF_8)))
.post(Entity.form(new Form().param("grant_type", "client_credentials")));

if (response.getStatus() == Status.OK.getStatusCode())
Expand Down Expand Up @@ -184,7 +187,7 @@ private DecodedJWT verifyAndDecodeAccessToken(String accessToken, Jwks jwks) thr
throw new OidcClientException("Access token key with kid '" + keyId + "' and use 'sig' not in JWKS");

Optional<Algorithm> algorithm = key.flatMap(JwksKey::toAlgorithm);
if (key.isEmpty())
if (algorithm.isEmpty())
{
throw new OidcClientException("Access token key with kid '" + keyId
+ "' has unsupported type (kty) / algorithm (alg) / key-size in JWKS");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,48 @@
*/
package dev.dsf.bpe.client.oidc;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.*;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.time.Duration;
import java.util.Base64;
import java.util.List;
import java.util.Set;

import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;

import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsServer;

import de.hsheilbronn.mi.utils.crypto.context.SSLContextFactory;
import de.hsheilbronn.mi.utils.crypto.io.PemReader;
import de.hsheilbronn.mi.utils.crypto.keystore.KeyStoreCreator;
import dev.dsf.bpe.api.client.oidc.OidcClientException;
import dev.dsf.bpe.integration.X509Certificates;
import dev.dsf.common.oidc.Jwks;
import dev.dsf.common.oidc.OidcConfiguration;
import jakarta.ws.rs.core.HttpHeaders;

@Ignore
// Needs keycloak service from 3dic-ttp dev setup, "Service accounts roles" needs to be activated for dic1-fhir client
public class OidcClientJerseyTest
{
@Rule
public final X509Certificates certificates = new X509Certificates();

@Rule
public final TokenEndpointServerResource tokenEndpointServerResource = new TokenEndpointServerResource();

// Needs keycloak service from 3dic-ttp dev setup, "Service accounts roles" needs to be activated for dic1-fhir
// client
@Ignore
@Test
public void getAccessToken() throws Exception
{
Expand All @@ -45,4 +71,126 @@ public void getAccessToken() throws Exception
char[] accessToken = client.asOidcClientWithDecodedJwt().getAccessToken();
assertNotNull(accessToken);
}
}

@Test
public void basicAuthorizationClientIdAndSecretShouldBeUrlEncoded() throws Exception
{
var clientId = "client/id:+";
var clientSecret = "client/s€cr€t:+";
var expectedAuthorizationHeaderValue = "Basic "
.concat(clientId.transform(id -> URLEncoder.encode(id, StandardCharsets.UTF_8)).concat(":")
.concat(clientSecret.transform(secret -> URLEncoder.encode(secret, StandardCharsets.UTF_8)))
.transform(s -> Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8))));
var client = new OidcClientJersey("https://localhost:" + tokenEndpointServerResource.getPort(),
"/.well-known/openid-configuration", clientId, clientSecret.toCharArray(),
certificates.getFhirServerCertificate().trustStore(), null, null, null, null, null, "Test Client",
Duration.ofSeconds(10), Duration.ofSeconds(5), true, Duration.ofSeconds(10), null, false);
var config = new OidcConfiguration("https://localhost:" + tokenEndpointServerResource.getPort(),
"https://localhost:" + tokenEndpointServerResource.getPort() + "/token", "https://localhost/jwks",
Set.of("client_credentials"));

// token is intentionally invalid as it is not the focus of this test
// and to simplify test code
assertThrows(OidcClientException.class, () -> client.getAccessTokenDecoded(config, new Jwks(List.of())));

assertEquals(expectedAuthorizationHeaderValue, tokenEndpointServerResource.getAuthorizationHeaderValue());
}

private class TokenEndpointServerResource extends ExternalResource
{
private HttpsServer server;
private String authorizationHeaderValue;

@Override
protected void before() throws Throwable
{
// Server will be created lazily when getPort() is first called
}

@Override
protected void after()
{
if (server != null)
{
server.stop(0);
}
}

int getPort() throws Exception
{
if (server == null)
{
server = createServer();
server.start();
}
return server.getAddress().getPort();
}

String getAuthorizationHeaderValue()
{
return authorizationHeaderValue;
}

private HttpsServer createServer() throws Exception
{
var newServer = HttpsServer.create(new InetSocketAddress("localhost", 0), 0);
var serverCertificate = certificates.getFhirServerCertificate();

var keyStorePassword = "server-password".toCharArray();
KeyStore keyStore = KeyStoreCreator.jksForPrivateKeyAndCertificateChain(serverCertificate.privateKey(),
keyStorePassword, serverCertificate.certificate(), serverCertificate.caCertificate());

newServer.setHttpsConfigurator(
new HttpsConfigurator(SSLContextFactory.createSSLContext(null, keyStore, keyStorePassword, "TLS")));

newServer.createContext("/token", exchange ->
{
authorizationHeaderValue = exchange.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);

byte[] response = "{\"access_token\":\"invalid\",\"expires_in\":300}".getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().set(HttpHeaders.CONTENT_TYPE, "application/json");
exchange.sendResponseHeaders(200, response.length);
try (OutputStream out = exchange.getResponseBody())
{
out.write(response);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
exchange.close();
});

return newServer;
}
}

@Test
public void testEncoding() throws Exception
{
/*
* RFC 6749, Appendix B - https://datatracker.ietf.org/doc/html/rfc6749#appendix-B
*
* [...]
*
* When parsing data from a payload using this media type, the names and values resulting from reversing the
* name/value encoding consequently need to be treated as octet sequences, to be decoded using the UTF-8
* character encoding scheme.
*
* For example, the value consisting of the six Unicode code points (1) U+0020 (SPACE), (2) U+0025 (PERCENT
* SIGN), (3) U+0026 (AMPERSAND), (4) U+002B (PLUS SIGN), (5) U+00A3 (POUND SIGN), and (6) U+20AC (EURO SIGN)
* would be encoded into the octet sequence below (using hexadecimal notation):
*
* 20 25 26 2B C2 A3 E2 82 AC
*
* and then represented in the payload as:
*
* +%25%26%2B%C2%A3%E2%82%AC
*/

String example = "\u0020\u0025\u0026\u002B\u00A3\u20AC";
String expected = "+%25%26%2B%C2%A3%E2%82%AC";

assertEquals(expected, URLEncoder.encode(example, StandardCharsets.UTF_8));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ public void startSendTaskTest() throws Exception
executePluginTest(createTestTask("SendTaskTest"));
}

@Test
public void startSendTaskErrorBoundaryEventTestThrow() throws Exception
{
executePluginTest(createTestTask("SendTaskErrorBoundaryEventTestThrow"));
}

@Test
public void startFieldInjectionTest() throws Exception
{
Expand Down
Loading