From e0e3f570e7959d9e80b4aae82ba85d0e8c2f53e2 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Wed, 11 Nov 2026 15:45:07 +0100 Subject: [PATCH 1/2] [Fix #1805] Refactor oauth code Signed-off-by: Francisco Javier Tirado Sarti --- .../impl/auth/AbstractAuthRequestBuilder.java | 2 +- .../sdk/impl/auth/AccessTokenProvider.java | 70 +++++++- .../impl/auth/AccessTokenProviderFactory.java | 32 ---- .../sdk/impl/auth/AuthHttpRequestInfo.java | 3 +- ...r.java => AuthHttpRequestInfoBuilder.java} | 41 ++--- .../sdk/impl/auth/ClientSecretBasic.java | 2 +- .../sdk/impl/auth/ClientSecretHandler.java | 4 +- .../sdk/impl/auth/ClientSecretPost.java | 2 +- .../sdk/impl/auth/CommonOAuthProvider.java | 47 +++-- .../sdk/impl/auth/JWTConverter.java | 2 +- .../sdk/impl/auth/JwtClientAssertion.java | 2 +- .../sdk/impl/http/DefaultHttpRequest.java | 59 ++++--- .../sdk/impl/http/URIDecodedBuilder.java | 2 +- .../impl/http/WorkflowHttpContentType.java | 54 ++++++ .../impl/http/WorkflowHttpRequestHelper.java | 31 +++- .../impl/http/WorkflowHttpRequestInfo.java | 19 +- .../sdk/impl/resources/HttpResource.java | 40 ++--- .../auth/JaxRSAccessTokenProvider.java | 164 ------------------ .../auth/JaxRSAccessTokenProviderFactory.java | 67 ------- .../jakarta/http/JakartaRequestExecutor.java | 25 ++- ...w.sdk.impl.auth.AccessTokenProviderFactory | 1 - impl/jwt-impl/pom.xml | 2 +- .../oauth/jackson/JacksonJWTConverter.java | 6 +- .../sdk/impl/http/jwt/JacksonJWTImplTest.java | 39 +---- .../impl/auth/OAuthRequestBuilderTest.java | 1 - .../test/OAuthHTTPWorkflowDefinitionTest.java | 146 +++++++++++----- .../OpenIDCHTTPWorkflowDefinitionTest.java | 109 ++++++++---- 27 files changed, 470 insertions(+), 502 deletions(-) delete mode 100644 impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AccessTokenProviderFactory.java rename impl/core/src/main/java/org/openworkflow/sdk/impl/auth/{HttpRequestInfoBuilder.java => AuthHttpRequestInfoBuilder.java} (66%) create mode 100644 impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpContentType.java delete mode 100644 impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/auth/JaxRSAccessTokenProvider.java delete mode 100644 impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/auth/JaxRSAccessTokenProviderFactory.java delete mode 100644 impl/http/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.auth.AccessTokenProviderFactory diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AbstractAuthRequestBuilder.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AbstractAuthRequestBuilder.java index 4c5af4b15..4e3e55fe1 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AbstractAuthRequestBuilder.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AbstractAuthRequestBuilder.java @@ -47,7 +47,7 @@ abstract class AbstractAuthRequestBuilder implements AuthRequestBuilder { protected final WorkflowApplication application; - protected final HttpRequestInfoBuilder requestBuilder = new HttpRequestInfoBuilder(); + protected final AuthHttpRequestInfoBuilder requestBuilder = new AuthHttpRequestInfoBuilder(); public AbstractAuthRequestBuilder(WorkflowApplication application) { this.application = application; diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AccessTokenProvider.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AccessTokenProvider.java index 75cc9c9ab..08bae24b6 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AccessTokenProvider.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AccessTokenProvider.java @@ -15,10 +15,76 @@ */ package org.openworkflow.sdk.impl.auth; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import org.openworkflow.sdk.impl.TaskContext; +import org.openworkflow.sdk.impl.WorkflowApplication; import org.openworkflow.sdk.impl.WorkflowContext; +import org.openworkflow.sdk.impl.http.WorkflowHttpContentType; +import org.openworkflow.sdk.impl.http.WorkflowHttpRequestHelper; +import org.openworkflow.sdk.impl.http.WorkflowHttpRequestInfo; -public interface AccessTokenProvider { - CompletableFuture validateAndGet(WorkflowContext workflow, TaskContext context); +class AccessTokenProvider { + + private final WorkflowApplication app; + private final List issuers; + private final AuthHttpRequestInfo requestInfo; + private final JWTConverter jwtConverter; + + AccessTokenProvider( + WorkflowApplication app, + AuthHttpRequestInfo requestInfo, + List issuers, + JWTConverter converter) { + this.app = app; + this.requestInfo = requestInfo; + this.issuers = issuers; + this.jwtConverter = converter; + } + + public CompletableFuture validateAndGet(WorkflowContext workflow, TaskContext context) { + WorkflowHttpRequestInfo httpRequestInfo = buildHttpRequestInfo(workflow, context); + return WorkflowHttpRequestHelper.executeRequest(httpRequestInfo, app, workflow, context) + .thenApply( + bytes -> { + JWT jwt = jwtConverter.fromToken(bytes); + if (issuers != null && !issuers.isEmpty()) { + + jwt.issuer() + .ifPresent( + issuer -> { + if (!issuers.contains(issuer)) { + throw new IllegalStateException("Token issuer is not valid: " + issuer); + } + }); + } + return jwt; + }); + } + + private WorkflowHttpRequestInfo buildHttpRequestInfo(WorkflowContext workflow, TaskContext task) { + WorkflowHttpRequestInfo.Builder builder = WorkflowHttpRequestInfo.builder("POST"); + builder.header("grant_type", requestInfo.grantType()); + builder.header("User-Agent", "OAuth2-Client-Credentials/1.0"); + builder.header("Cache-Control", "no-cache"); + builder.header("Accept", "application/json"); + for (var entry : requestInfo.headers().entrySet()) { + String headerValue = entry.getValue().apply(workflow, task); + if (headerValue != null) { + builder.header(entry.getKey(), headerValue); + } + } + builder.header(WorkflowHttpContentType.HEADER_NAME, requestInfo.contentType().toString()); + Map body = new HashMap(); + body.put("grant_type", requestInfo.grantType()); + requestInfo.queryParams().forEach((key, value) -> body.put(key, value.apply(workflow, task))); + requestInfo + .clientAuthParams() + .forEach((key, value) -> body.put(key, value.apply(workflow, task))); + return builder + .body(app.modelFactory().from(body)) + .build(requestInfo.uri().apply(workflow, task)); + } } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AccessTokenProviderFactory.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AccessTokenProviderFactory.java deleted file mode 100644 index a6f24b3b8..000000000 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AccessTokenProviderFactory.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2020-Present The Open Workflow Specification Authors - * - * 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 org.openworkflow.sdk.impl.auth; - -import java.util.List; -import org.openworkflow.sdk.impl.ServicePriority; -import org.openworkflow.sdk.impl.WorkflowApplication; - -public interface AccessTokenProviderFactory - extends ServicePriority, AutoCloseable { - - AccessTokenProvider build( - WorkflowApplication app, - AuthHttpRequestInfo requestInfo, - List issuers, - JWTConverter converter); - - default void close() {} -} diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfo.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfo.java index c3e119eaf..2732d4a0b 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfo.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfo.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Optional; import org.openworkflow.sdk.impl.WorkflowValueResolver; +import org.openworkflow.sdk.impl.http.WorkflowHttpContentType; public record AuthHttpRequestInfo( Map> headers, @@ -28,4 +29,4 @@ public record AuthHttpRequestInfo( Optional> revocationUri, Optional> introspectionUri, String grantType, - String contentType) {} + WorkflowHttpContentType contentType) {} diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/HttpRequestInfoBuilder.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfoBuilder.java similarity index 66% rename from impl/core/src/main/java/org/openworkflow/sdk/impl/auth/HttpRequestInfoBuilder.java rename to impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfoBuilder.java index 740c8cd16..37912b5f6 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/HttpRequestInfoBuilder.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfoBuilder.java @@ -15,8 +15,6 @@ */ package org.openworkflow.sdk.impl.auth; -import static org.openworkflow.sdk.api.types.OAuth2TokenRequest.Oauth2TokenRequestEncoding.APPLICATION_X_WWW_FORM_URLENCODED; - import java.net.URI; import java.util.HashMap; import java.util.Map; @@ -24,8 +22,9 @@ import java.util.Optional; import org.openworkflow.sdk.api.types.OAuth2TokenRequest; import org.openworkflow.sdk.impl.WorkflowValueResolver; +import org.openworkflow.sdk.impl.http.WorkflowHttpContentType; -class HttpRequestInfoBuilder { +class AuthHttpRequestInfoBuilder { private Map> headers; @@ -41,75 +40,73 @@ class HttpRequestInfoBuilder { private String grantType; - private String contentType; + private WorkflowHttpContentType contentType; - HttpRequestInfoBuilder() { + AuthHttpRequestInfoBuilder() { headers = new HashMap<>(); queryParams = new HashMap<>(); clientAuthParams = new HashMap<>(); } - HttpRequestInfoBuilder addHeader(String key, String token) { + AuthHttpRequestInfoBuilder addHeader(String key, String token) { headers.put(key, (w, t, m) -> token); return this; } - HttpRequestInfoBuilder addHeader(String key, WorkflowValueResolver token) { + AuthHttpRequestInfoBuilder addHeader(String key, WorkflowValueResolver token) { headers.put(key, token); return this; } - HttpRequestInfoBuilder addQueryParam(String key, String token) { + AuthHttpRequestInfoBuilder addQueryParam(String key, String token) { queryParams.put(key, (w, t, m) -> token); return this; } - HttpRequestInfoBuilder addQueryParam(String key, WorkflowValueResolver token) { + AuthHttpRequestInfoBuilder addQueryParam(String key, WorkflowValueResolver token) { queryParams.put(key, token); return this; } - HttpRequestInfoBuilder addClientAuthParam(String key, String token) { + AuthHttpRequestInfoBuilder addClientAuthParam(String key, String token) { clientAuthParams.put(key, (w, t, m) -> token); return this; } - HttpRequestInfoBuilder addClientAuthParam(String key, WorkflowValueResolver token) { + AuthHttpRequestInfoBuilder addClientAuthParam(String key, WorkflowValueResolver token) { clientAuthParams.put(key, token); return this; } - HttpRequestInfoBuilder withUri(WorkflowValueResolver uri) { + AuthHttpRequestInfoBuilder withUri(WorkflowValueResolver uri) { this.uri = uri; return this; } - HttpRequestInfoBuilder withRevocationUri(Optional> revocationUri) { + AuthHttpRequestInfoBuilder withRevocationUri(Optional> revocationUri) { this.revocationUri = revocationUri; return this; } - HttpRequestInfoBuilder withIntrospectionUri( + AuthHttpRequestInfoBuilder withIntrospectionUri( Optional> introspectionUri) { this.introspectionUri = introspectionUri; return this; } - HttpRequestInfoBuilder withContentType(OAuth2TokenRequest oAuth2TokenRequest) { + AuthHttpRequestInfoBuilder withContentType(OAuth2TokenRequest oAuth2TokenRequest) { if (oAuth2TokenRequest != null) { - this.contentType = oAuth2TokenRequest.getEncoding().value(); + this.contentType = WorkflowHttpContentType.fromValue(oAuth2TokenRequest.getEncoding()); } return this; } - HttpRequestInfoBuilder withContentType(String contentType) { - if (contentType != null) { - this.contentType = contentType; - } + AuthHttpRequestInfoBuilder withContentType(String contentType) { + this.contentType = WorkflowHttpContentType.fromValue(contentType).orElse(null); return this; } - HttpRequestInfoBuilder withGrantType(String grantType) { + AuthHttpRequestInfoBuilder withGrantType(String grantType) { this.grantType = grantType; return this; } @@ -118,7 +115,7 @@ AuthHttpRequestInfo build() { Objects.requireNonNull(uri, "URI must be set before building the request"); Objects.requireNonNull(grantType, "Grant type must be set before building the request"); if (contentType == null) { - contentType = APPLICATION_X_WWW_FORM_URLENCODED.value(); + contentType = WorkflowHttpContentType.APPLICATION_X_WWW_FORM_URLENCODED; } return new AuthHttpRequestInfo( headers, diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/ClientSecretBasic.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/ClientSecretBasic.java index 3053b5afc..8ed4f2ad8 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/ClientSecretBasic.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/ClientSecretBasic.java @@ -31,7 +31,7 @@ class ClientSecretBasic extends ClientSecretHandler { protected ClientSecretBasic( - WorkflowApplication application, HttpRequestInfoBuilder requestBuilder) { + WorkflowApplication application, AuthHttpRequestInfoBuilder requestBuilder) { super(application, requestBuilder); } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/ClientSecretHandler.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/ClientSecretHandler.java index c80df8021..3b7f2ab40 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/ClientSecretHandler.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/ClientSecretHandler.java @@ -28,10 +28,10 @@ abstract class ClientSecretHandler { protected final WorkflowApplication application; - protected final HttpRequestInfoBuilder requestBuilder; + protected final AuthHttpRequestInfoBuilder requestBuilder; protected ClientSecretHandler( - WorkflowApplication application, HttpRequestInfoBuilder requestBuilder) { + WorkflowApplication application, AuthHttpRequestInfoBuilder requestBuilder) { this.application = application; this.requestBuilder = requestBuilder; } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/ClientSecretPost.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/ClientSecretPost.java index 8e05722df..9361d7dab 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/ClientSecretPost.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/ClientSecretPost.java @@ -30,7 +30,7 @@ class ClientSecretPost extends ClientSecretHandler { protected ClientSecretPost( - WorkflowApplication application, HttpRequestInfoBuilder requestBuilder) { + WorkflowApplication application, AuthHttpRequestInfoBuilder requestBuilder) { super(application, requestBuilder); } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/CommonOAuthProvider.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/CommonOAuthProvider.java index 768e23424..c3329158e 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/CommonOAuthProvider.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/CommonOAuthProvider.java @@ -28,20 +28,19 @@ import org.openworkflow.sdk.impl.TaskContext; import org.openworkflow.sdk.impl.WorkflowApplication; import org.openworkflow.sdk.impl.WorkflowContext; -import org.openworkflow.sdk.impl.WorkflowValueResolver; public abstract class CommonOAuthProvider implements AuthProvider { - private final WorkflowValueResolver tokenProvider; + private final AccessTokenProvider tokenProvider; - protected CommonOAuthProvider(WorkflowValueResolver tokenProvider) { + protected CommonOAuthProvider(AccessTokenProvider tokenProvider) { this.tokenProvider = tokenProvider; } @Override public CompletableFuture content( WorkflowContext workflow, TaskContext task, URI uri, String method) { - return tokenProvider.apply(workflow, task).validateAndGet(workflow, task).thenApply(JWT::token); + return tokenProvider.validateAndGet(workflow, task).thenApply(JWT::token); } @Override @@ -54,7 +53,7 @@ protected static OAuth2AuthenticationData fillFromMap( return data; } - protected static WorkflowValueResolver accessToken( + protected static AccessTokenProvider accessToken( WorkflowApplication app, Workflow workflow, OAuth2AuthenticationData authenticationData, @@ -68,31 +67,25 @@ protected static WorkflowValueResolver accessToken( throw new IllegalStateException("Both policy and secret are null"); } - private static WorkflowValueResolver build( + private static AccessTokenProvider build( OAuth2AuthenticationData authenticationData, - AuthRequestBuilder authBuilder, + AuthRequestBuilder authBuilder, WorkflowApplication app) { - AccessTokenProvider tokenProvider = - app.serviceLoadedClass(AccessTokenProviderFactory.class) - .build( - app, - authBuilder.apply(authenticationData), - authenticationData.getIssuers(), - app.serviceLoadedClass(JWTConverter.class)); - return (w, t, m) -> tokenProvider; + return new AccessTokenProvider( + app, + authBuilder.apply((T) authenticationData), + authenticationData.getIssuers(), + app.serviceLoadedClass(JWTConverter.class)); } - private static WorkflowValueResolver build( - String secretName, AuthRequestBuilder authBuilder, WorkflowApplication app) { - return (w, t, m) -> { - Map secret = secret(app, secretName); - String issuers = (String) secret.get("issuers"); - return app.serviceLoadedClass(AccessTokenProviderFactory.class) - .build( - app, - authBuilder.apply(secret), - issuers != null ? Arrays.asList(issuers.split(",")) : null, - app.serviceLoadedClass(JWTConverter.class)); - }; + private static AccessTokenProvider build( + String secretName, AuthRequestBuilder authBuilder, WorkflowApplication app) { + Map secret = secret(app, secretName); + String issuers = (String) secret.get("issuers"); + return new AccessTokenProvider( + app, + authBuilder.apply(secret), + issuers != null ? Arrays.asList(issuers.split(",")) : null, + app.serviceLoadedClass(JWTConverter.class)); } } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/JWTConverter.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/JWTConverter.java index 30f9a9869..9d927673e 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/JWTConverter.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/JWTConverter.java @@ -25,5 +25,5 @@ public interface JWTConverter extends ServicePriority { * @param token the JWT token string * @return a JWT object containing the token and its claims */ - JWT fromToken(String token) throws IllegalArgumentException; + JWT fromToken(byte[] token) throws IllegalArgumentException; } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/JwtClientAssertion.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/JwtClientAssertion.java index 2000f6de5..d17e0d5bb 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/JwtClientAssertion.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/JwtClientAssertion.java @@ -43,7 +43,7 @@ class JwtClientAssertion extends ClientSecretHandler { protected JwtClientAssertion( - WorkflowApplication application, HttpRequestInfoBuilder requestBuilder) { + WorkflowApplication application, AuthHttpRequestInfoBuilder requestBuilder) { super(application, requestBuilder); } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/DefaultHttpRequest.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/DefaultHttpRequest.java index 0a4e3a6a6..50e2d7588 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/DefaultHttpRequest.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/DefaultHttpRequest.java @@ -18,6 +18,7 @@ import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublisher; import java.net.http.HttpRequest.Builder; import java.net.http.HttpResponse; import java.util.Map; @@ -45,19 +46,8 @@ public DefaultHttpRequest( public CompletableFuture invoke( WorkflowContext workflowContext, TaskContext taskContext) { Builder builder = HttpRequest.newBuilder(); - WorkflowModel body = httpInfo.body(); - builder.method( - httpInfo.method(), - body == null || body.isNull() - ? HttpRequest.BodyPublishers.noBody() - : HttpRequest.BodyPublishers.ofByteArray( - body.asJson() - .orElseThrow( - () -> - new IllegalStateException( - "Underlying model needs to support json in order to use http")))); + builder.method(httpInfo.method(), buildBody()); Map headers = httpInfo.headers(); - // add default content type, it might be override by specified headers. builder.header("Content-Type", "application/json"); headers.forEach((k, v) -> builder.setHeader(k, v.toString())); @@ -85,17 +75,46 @@ public CompletableFuture invoke( } private URI addQueryParams(URI uri, Map queryParams) { - String addedQuery = - queryParams.entrySet().stream() - .map( - entry -> - URIDecodedBuilder.encode(entry.getKey()) - + "=" - + URIDecodedBuilder.encode(entry.getValue())) - .collect(Collectors.joining("&")); + String addedQuery = encodeMap(queryParams); String rawQuery = uri.getRawQuery(); return new URIDecodedBuilder(uri) .query(rawQuery == null || rawQuery.isEmpty() ? addedQuery : rawQuery + "&" + addedQuery) .build(); } + + private BodyPublisher buildBody() { + WorkflowModel body = httpInfo.body(); + if (body == null || body.isNull()) { + return HttpRequest.BodyPublishers.noBody(); + } + return switch (httpInfo.contentType()) { + case WorkflowHttpContentType.APPLICATION_JSON -> + HttpRequest.BodyPublishers.ofByteArray( + body.asJson() + .orElseThrow( + () -> + new IllegalStateException( + "Underlying model needs to support json in order to use application-json content type"))); + case WorkflowHttpContentType.APPLICATION_X_WWW_FORM_URLENCODED -> + HttpRequest.BodyPublishers.ofString( + encodeMap( + httpInfo + .body() + .asMap() + .orElseThrow( + () -> + new IllegalStateException( + "Underlying model needs to support asMap in order to use form urlencoded content type")))); + }; + } + + private String encodeMap(Map map) { + return map.entrySet().stream() + .map( + entry -> + URIDecodedBuilder.encode(entry.getKey()) + + "=" + + URIDecodedBuilder.encode(entry.getValue())) + .collect(Collectors.joining("&")); + } } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/URIDecodedBuilder.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/URIDecodedBuilder.java index fb589f701..5670c3b95 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/URIDecodedBuilder.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/URIDecodedBuilder.java @@ -83,6 +83,6 @@ public URI build() { } public static String encode(Object part) { - return URLEncoder.encode(part.toString(), StandardCharsets.UTF_8).replace("+", "%20"); + return URLEncoder.encode(part.toString(), StandardCharsets.UTF_8); } } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpContentType.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpContentType.java new file mode 100644 index 000000000..ae7bd9a8c --- /dev/null +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpContentType.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020-Present The Open Workflow Specification Authors + * + * 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 org.openworkflow.sdk.impl.http; + +import java.util.Optional; +import org.openworkflow.sdk.api.types.OAuth2TokenRequest.Oauth2TokenRequestEncoding; + +public enum WorkflowHttpContentType { + APPLICATION_JSON("application/json"), + APPLICATION_X_WWW_FORM_URLENCODED("application/x-www-form-urlencoded"); + + public static final String HEADER_NAME = "content-type"; + + private final String value; + + private WorkflowHttpContentType(String value) { + this.value = value; + } + + public static WorkflowHttpContentType fromValue(Oauth2TokenRequestEncoding encoding) { + return switch (encoding) { + case null -> WorkflowHttpContentType.APPLICATION_JSON; + case APPLICATION_JSON -> WorkflowHttpContentType.APPLICATION_JSON; + case APPLICATION_X_WWW_FORM_URLENCODED -> + WorkflowHttpContentType.APPLICATION_X_WWW_FORM_URLENCODED; + }; + } + + public static Optional fromValue(String value) { + for (WorkflowHttpContentType type : WorkflowHttpContentType.values()) { + if (type.value.equalsIgnoreCase(value)) { + return Optional.of(type); + } + } + return Optional.empty(); + } + + public String toString() { + return value; + } +} diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestHelper.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestHelper.java index 946da813e..5e2595ae3 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestHelper.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestHelper.java @@ -36,10 +36,8 @@ public class WorkflowHttpRequestHelper { public static CompletableFuture executeRequest( WorkflowHttpRequestInfo requestInfo, WorkflowContext workflow, TaskContext taskContext) { - WorkflowApplication app = workflow.definition().application(); - return app.httpRequestFactory() - .request(app, requestInfo) - .invoke(workflow, taskContext) + return asWorkflowHtppResponse( + requestInfo, workflow.definition().application(), workflow, taskContext) .thenApply( response -> { try { @@ -56,6 +54,31 @@ public static CompletableFuture executeRequest( }); } + public static CompletableFuture executeRequest( + WorkflowHttpRequestInfo requestInfo, + WorkflowApplication app, + WorkflowContext workflow, + TaskContext taskContext) { + return asWorkflowHtppResponse(requestInfo, app, workflow, taskContext) + .thenApply( + response -> { + try { + validateStatus(taskContext, requestInfo, response); + return response.asByteArray(); + } finally { + response.close(); + } + }); + } + + public static CompletableFuture asWorkflowHtppResponse( + WorkflowHttpRequestInfo requestInfo, + WorkflowApplication app, + WorkflowContext workflow, + TaskContext taskContext) { + return app.httpRequestFactory().request(app, requestInfo).invoke(workflow, taskContext); + } + public static WorkflowException fromError(Throwable ex, TaskContext task) { throw new WorkflowException( WorkflowError.communication(errorCodeFromException(ex.getCause()), task, ex).build(), ex); diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestInfo.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestInfo.java index a6be9a960..53f01e4a6 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestInfo.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestInfo.java @@ -19,6 +19,7 @@ import java.time.Instant; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; +import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; @@ -42,11 +43,17 @@ public static Builder builder(String method) { return new Builder(method); } + public WorkflowHttpContentType contentType() { + return WorkflowHttpContentType.fromValue( + (String) headers.get(WorkflowHttpContentType.HEADER_NAME)) + .orElse(WorkflowHttpContentType.APPLICATION_JSON); + } + public static class Builder { private final String method; private boolean redirect; - private Map headers = Map.of(); - private Map queryParams = Map.of(); + private Map headers; + private Map queryParams; private WorkflowModel body; private AuthProvider auth; private Long ifModifiedSince; @@ -70,6 +77,14 @@ public Builder headers(Map headers) { return this; } + public Builder header(String name, Object value) { + if (headers == null) { + headers = new HashMap(); + } + headers.put(name, value); + return this; + } + public Builder path(URI path) { this.path = path; return this; diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/resources/HttpResource.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/resources/HttpResource.java index 28d75c05b..aa80229ab 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/resources/HttpResource.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/resources/HttpResource.java @@ -25,9 +25,8 @@ import org.openworkflow.sdk.impl.TaskContext; import org.openworkflow.sdk.impl.WorkflowApplication; import org.openworkflow.sdk.impl.WorkflowContext; -import org.openworkflow.sdk.impl.WorkflowError; -import org.openworkflow.sdk.impl.WorkflowException; import org.openworkflow.sdk.impl.auth.AuthProvider; +import org.openworkflow.sdk.impl.http.WorkflowHttpRequestHelper; import org.openworkflow.sdk.impl.http.WorkflowHttpRequestInfo; public class HttpResource implements ExternalResourceHandler { @@ -45,29 +44,11 @@ public HttpResource(WorkflowApplication application, URI url, AuthProvider auth) @Override public CompletableFuture load(WorkflowContext workflowContext, TaskContext taskContext) { - WorkflowHttpRequestInfo.Builder info = WorkflowHttpRequestInfo.builder("GET").auth(auth); - return application - .httpRequestFactory() - .request(application, info.build(url)) - .invoke(workflowContext, taskContext) - .thenApply( - response -> { - try (response) { - int status = response.status(); - if (status < 200 || status >= 300) { - throw new WorkflowException( - WorkflowError.communication( - status, - taskContext, - "Failed to download resource from " - + url - + ". Reason " - + response.reasonPhrase()) - .build()); - } - return response.asByteArray(); - } - }); + return WorkflowHttpRequestHelper.executeRequest( + WorkflowHttpRequestInfo.builder("GET").auth(auth).build(url), + application, + workflowContext, + taskContext); } public String name() { @@ -85,12 +66,11 @@ public String name() { public CompletableFuture shouldReload( Instant lastUpdate, WorkflowContext workflowContext, TaskContext taskContext) { final long millis = lastUpdate.toEpochMilli(); - return application - .httpRequestFactory() - .request( + return WorkflowHttpRequestHelper.asWorkflowHtppResponse( + WorkflowHttpRequestInfo.builder("HEAD").ifModifiedSince(millis).auth(auth).build(url), application, - WorkflowHttpRequestInfo.builder("HEAD").ifModifiedSince(millis).auth(auth).build(url)) - .invoke(workflowContext, taskContext) + workflowContext, + taskContext) .thenApply( response -> { try (response) { diff --git a/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/auth/JaxRSAccessTokenProvider.java b/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/auth/JaxRSAccessTokenProvider.java deleted file mode 100644 index 245acd594..000000000 --- a/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/auth/JaxRSAccessTokenProvider.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2020-Present The Open Workflow Specification Authors - * - * 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 org.openworkflow.sdk.impl.jakarta.auth; - -import static org.openworkflow.sdk.api.types.OAuth2TokenRequest.Oauth2TokenRequestEncoding.APPLICATION_X_WWW_FORM_URLENCODED; - -import jakarta.ws.rs.ProcessingException; -import jakarta.ws.rs.client.Client; -import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.client.Invocation; -import jakarta.ws.rs.client.ResponseProcessingException; -import jakarta.ws.rs.client.WebTarget; -import jakarta.ws.rs.core.Form; -import jakarta.ws.rs.core.GenericType; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import org.openworkflow.sdk.impl.TaskContext; -import org.openworkflow.sdk.impl.WorkflowApplication; -import org.openworkflow.sdk.impl.WorkflowContext; -import org.openworkflow.sdk.impl.WorkflowError; -import org.openworkflow.sdk.impl.WorkflowException; -import org.openworkflow.sdk.impl.auth.AccessTokenProvider; -import org.openworkflow.sdk.impl.auth.AuthHttpRequestInfo; -import org.openworkflow.sdk.impl.auth.JWT; -import org.openworkflow.sdk.impl.auth.JWTConverter; -import org.openworkflow.sdk.impl.jakarta.http.HttpClientResolver; - -// TODO this class should be refactor to be included in the core and use WorkflowHttpRequest -class JaxRSAccessTokenProvider implements AccessTokenProvider { - - private final WorkflowApplication app; - private final Client defaultClient; - private final List issuers; - private final AuthHttpRequestInfo requestInfo; - private final JWTConverter jwtConverter; - - JaxRSAccessTokenProvider( - WorkflowApplication app, - Client client, - AuthHttpRequestInfo requestInfo, - List issuers, - JWTConverter converter) { - this.app = app; - this.defaultClient = client; - this.requestInfo = requestInfo; - this.issuers = issuers; - this.jwtConverter = converter; - } - - @Override - public CompletableFuture validateAndGet(WorkflowContext workflow, TaskContext context) { - return CompletableFuture.supplyAsync( - () -> { - Map token = invoke(workflow, context); - JWT jwt = jwtConverter.fromToken((String) token.get("access_token")); - if (issuers != null && !issuers.isEmpty()) { - jwt.issuer() - .ifPresent( - issuer -> { - if (!issuers.contains(issuer)) { - throw new IllegalStateException("Token issuer is not valid: " + issuer); - } - }); - } - return jwt; - }, - app.executorService()); - } - - private Map invoke(WorkflowContext workflowContext, TaskContext taskContext) { - try (Response response = executeRequest(workflowContext, taskContext)) { - int status = response.getStatus(); - if (status < 200 || status >= 300) { - StringBuilder message = new StringBuilder("Failed to obtain token. Error code: " + status); - if (response.hasEntity()) { - message.append(". Message: ").append(response.readEntity(String.class)); - } - throw new WorkflowException( - WorkflowError.communication(status, taskContext, message.toString()).build()); - } - return response.readEntity(new GenericType<>() {}); - } catch (ResponseProcessingException e) { - throw new WorkflowException( - WorkflowError.communication( - e.getResponse().getStatus(), - taskContext, - "Failed to process response: " + e.getMessage()) - .build(), - e); - } catch (ProcessingException e) { - throw new WorkflowException( - WorkflowError.communication( - taskContext, "Failed to connect or process request: " + e.getMessage()) - .build(), - e); - } - } - - private Response executeRequest(WorkflowContext workflow, TaskContext task) { - Client client = - app.additionalObject(HttpClientResolver.HTTP_CLIENT_PROVIDER, workflow, task) - .orElse(defaultClient); - WebTarget target = client.target(requestInfo.uri().apply(workflow, task)); - Invocation.Builder builder = commonHeaders(target, workflow, task); - builder.header("grant_type", requestInfo.grantType()); - - Entity entity; - if (requestInfo.contentType().equals(APPLICATION_X_WWW_FORM_URLENCODED.value())) { - Form form = new Form(); - form.param("grant_type", requestInfo.grantType()); - requestInfo - .queryParams() - .forEach((key, value) -> form.param(key, value.apply(workflow, task))); - requestInfo - .clientAuthParams() - .forEach((key, value) -> form.param(key, value.apply(workflow, task))); - entity = Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED); - } else { - Map jsonData = new HashMap<>(); - jsonData.put("grant_type", requestInfo.grantType()); - requestInfo - .queryParams() - .forEach((key, value) -> jsonData.put(key, value.apply(workflow, task))); - requestInfo - .clientAuthParams() - .forEach((key, value) -> jsonData.put(key, value.apply(workflow, task))); - entity = Entity.entity(jsonData, MediaType.APPLICATION_JSON); - } - - return builder.post(entity); - } - - private Invocation.Builder commonHeaders( - WebTarget target, WorkflowContext workflow, TaskContext task) { - Invocation.Builder builder = target.request(MediaType.APPLICATION_JSON); - builder.header("User-Agent", "OAuth2-Client-Credentials/1.0"); - builder.header("Accept", MediaType.APPLICATION_JSON); - builder.header("Cache-Control", "no-cache"); - for (var entry : requestInfo.headers().entrySet()) { - String headerValue = entry.getValue().apply(workflow, task); - if (headerValue != null) { - builder.header(entry.getKey(), headerValue); - } - } - return builder; - } -} diff --git a/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/auth/JaxRSAccessTokenProviderFactory.java b/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/auth/JaxRSAccessTokenProviderFactory.java deleted file mode 100644 index b83265f68..000000000 --- a/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/auth/JaxRSAccessTokenProviderFactory.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2020-Present The Open Workflow Specification Authors - * - * 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 org.openworkflow.sdk.impl.jakarta.auth; - -import jakarta.ws.rs.client.Client; -import jakarta.ws.rs.client.ClientBuilder; -import java.util.List; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import org.openworkflow.sdk.impl.WorkflowApplication; -import org.openworkflow.sdk.impl.auth.AccessTokenProvider; -import org.openworkflow.sdk.impl.auth.AccessTokenProviderFactory; -import org.openworkflow.sdk.impl.auth.AuthHttpRequestInfo; -import org.openworkflow.sdk.impl.auth.JWTConverter; -import org.openworkflow.sdk.impl.jakarta.http.JakartaRequestExecutorFactory; - -public class JaxRSAccessTokenProviderFactory implements AccessTokenProviderFactory { - - private static class DefaultHolder { - - private static final Lock clientLock = new ReentrantLock(); - private static volatile Client client; - - private static final Client client() { - if (client == null) { - clientLock.lock(); - try { - if (client == null) { - client = ClientBuilder.newClient(); - } - } finally { - clientLock.unlock(); - } - } - return client; - } - } - - @Override - public AccessTokenProvider build( - WorkflowApplication app, - AuthHttpRequestInfo requestInfo, - List issuers, - JWTConverter converter) { - return new JaxRSAccessTokenProvider( - app, - app.httpRequestFactory() instanceof JakartaRequestExecutorFactory factory - ? factory.client() - : DefaultHolder.client(), - requestInfo, - issuers, - converter); - } -} diff --git a/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/http/JakartaRequestExecutor.java b/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/http/JakartaRequestExecutor.java index 04f5f0772..c55a507de 100644 --- a/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/http/JakartaRequestExecutor.java +++ b/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/http/JakartaRequestExecutor.java @@ -17,8 +17,11 @@ import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Invocation.Builder; import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Form; +import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import java.util.Collection; import java.util.List; @@ -89,12 +92,32 @@ public CompletableFuture invoke( taskContext)); } + private Entity buildBody(HttpModelConverter converter) { + return switch (requestInfo.contentType()) { + case APPLICATION_JSON -> converter.toEntity(requestInfo.body()); + case APPLICATION_X_WWW_FORM_URLENCODED -> buildForm(); + }; + } + + private Entity
buildForm() { + Form form = new Form(); + requestInfo + .body() + .asMap() + .orElseThrow( + () -> + new IllegalStateException( + "Workflow model should support asMap in order form_url_enconded to work")) + .forEach((k, v) -> form.param(k, v.toString())); + return Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED); + } + private CompletableFuture doRequest( Builder request, HttpModelConverter converter, TaskContext task) { CompletionStage future = requestInfo.body() == null || requestInfo.body().isNull() ? request.rx().method(requestInfo.method()) - : request.rx().method(requestInfo.method(), converter.toEntity(requestInfo.body())); + : request.rx().method(requestInfo.method(), buildBody(converter)); return future .exceptionallyCompose( ex -> diff --git a/impl/http/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.auth.AccessTokenProviderFactory b/impl/http/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.auth.AccessTokenProviderFactory deleted file mode 100644 index 62abf8f92..000000000 --- a/impl/http/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.auth.AccessTokenProviderFactory +++ /dev/null @@ -1 +0,0 @@ -org.openworkflow.sdk.impl.jakarta.auth.JaxRSAccessTokenProviderFactory \ No newline at end of file diff --git a/impl/jwt-impl/pom.xml b/impl/jwt-impl/pom.xml index 07a53570c..d7d31c370 100644 --- a/impl/jwt-impl/pom.xml +++ b/impl/jwt-impl/pom.xml @@ -21,7 +21,7 @@ org.junit.jupiter - junit-jupiter-api + junit-jupiter-engine org.assertj diff --git a/impl/jwt-impl/src/main/java/org/openworkflow/sdk/impl/executors/http/oauth/jackson/JacksonJWTConverter.java b/impl/jwt-impl/src/main/java/org/openworkflow/sdk/impl/executors/http/oauth/jackson/JacksonJWTConverter.java index edaff4f29..d7611bb23 100644 --- a/impl/jwt-impl/src/main/java/org/openworkflow/sdk/impl/executors/http/oauth/jackson/JacksonJWTConverter.java +++ b/impl/jwt-impl/src/main/java/org/openworkflow/sdk/impl/executors/http/oauth/jackson/JacksonJWTConverter.java @@ -27,7 +27,11 @@ public class JacksonJWTConverter implements JWTConverter { @Override - public JWT fromToken(String token) throws IllegalArgumentException { + public JWT fromToken(byte[] bytes) throws IllegalArgumentException { + + Map map = + JsonUtils.mapper().readValue(bytes, new TypeReference>() {}); + String token = (String) map.get("access_token"); if (token == null || token.isBlank()) { throw new IllegalArgumentException("JWT token must not be null or blank"); } diff --git a/impl/jwt-impl/src/test/java/org/openworkflow/sdk/impl/http/jwt/JacksonJWTImplTest.java b/impl/jwt-impl/src/test/java/org/openworkflow/sdk/impl/http/jwt/JacksonJWTImplTest.java index 5e63129e1..131209fc5 100644 --- a/impl/jwt-impl/src/test/java/org/openworkflow/sdk/impl/http/jwt/JacksonJWTImplTest.java +++ b/impl/jwt-impl/src/test/java/org/openworkflow/sdk/impl/http/jwt/JacksonJWTImplTest.java @@ -27,18 +27,21 @@ import org.junit.jupiter.api.Test; import org.openworkflow.sdk.impl.auth.JWT; import org.openworkflow.sdk.impl.executors.http.oauth.jackson.JacksonJWTConverter; +import org.openworkflow.sdk.impl.jackson.JsonUtils; public class JacksonJWTImplTest { - private static String JWT_TOKEN = + private static final String BEARER_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJTY1c4RGI5RThtSnlvTUNqZHVtek5VR21FcG9MYURDb19ralZkVHJ2NDdVIn0.eyJleHAiOjE3NTYxNDgyNTcsImlhdCI6MTc1NjE0Nzk1NywianRpIjoiOWU4YzZjMWItZDBmZS00NGNhLThlOTgtNzNkZTY4OTdjYzE4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDg4L3JlYWxtcy90ZXN0LXJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImExZDdlMWVlLTVmZjMtNDc3Yi1hMmRhLTNhNDZkYThlZmRkMSIsInR5cCI6IkJlYXJlciIsImF6cCI6InNlcnZlcmxlc3Mtd29ya2Zsb3ciLCJzZXNzaW9uX3N0YXRlIjoiNDdiYzIxZDEtYTMyYi00OGJlLWI2OWQtZDM5MjE5MjViZTlmIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtdGVzdC1yZWFsbSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6InByb2ZpbGUgZW1haWwiLCJzaWQiOiI0N2JjMjFkMS1hMzJiLTQ4YmUtYjY5ZC1kMzkyMTkyNWJlOWYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJEbWl0cmlpIFRpa2hvbWlyb3YiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2ZXJsZXNzLXdvcmtmbG93LXRlc3QiLCJnaXZlbl9uYW1lIjoiRG1pdHJpaSIsImZhbWlseV9uYW1lIjoiVGlraG9taXJvdiIsImVtYWlsIjoiYW55QGV4YW1wbGUub3JnIn0.bY9DqVt5jPcfsY5-tL4JbXXptnyFBBLLKuViBH3xQBXpHSmtXKK78BjBdUOcCAg6SMatdQ4XK13RJJNPpjPDCkuBeLDMwsdCrzKcfaUWY7JCMfam4gIiC989m_S8y8jtJovDst8sjIkn95f5izuuiAxRYw69_IcY__cfBw7k0OhL7Y_YXCcwwz7l2yrbplmpLiakTTuzhbiCER5EPohguy9BkYG_u2RDUZg0Rvy3EzbyVhTQQyfKRFjoAxTa1Se484n2lXqgMn1JHTZLwrgXAjcMDVaktktLb_cn276ygAeuqPj7dOsQGEoLR-8enRz1eZKBPgO70LNqGkkkyHiyOA"; + private static final byte[] JWT_TOKEN = + JsonUtils.mapper().writeValueAsBytes(Map.of("access_token", BEARER_TOKEN)); private static final JacksonJWTConverter converter = new JacksonJWTConverter(); @Test void tokenRoundTrip() { JWT jwt = converter.fromToken(JWT_TOKEN); - assertEquals(JWT_TOKEN, jwt.token()); + assertEquals(BEARER_TOKEN, jwt.token()); } @Test @@ -85,36 +88,4 @@ void typedClaimAccess() { void nullToken() { assertThrows(IllegalArgumentException.class, () -> converter.fromToken(null)); } - - @Test - void blankToken() { - assertThrows(IllegalArgumentException.class, () -> converter.fromToken(" ")); - } - - @Test - void wrongPartsCount() { - assertThrows(IllegalArgumentException.class, () -> converter.fromToken("a.b")); - assertThrows(IllegalArgumentException.class, () -> converter.fromToken("a.b.c.d")); - } - - @Test - void badBase64Header() { - String bad = "###." + JWT_TOKEN.split("\\.")[1] + "." + JWT_TOKEN.split("\\.")[2]; - assertThrows(IllegalArgumentException.class, () -> converter.fromToken(bad)); - } - - @Test - void badBase64Payload() { - String[] p = JWT_TOKEN.split("\\."); - String bad = p[0] + ".###." + p[2]; - assertThrows(IllegalArgumentException.class, () -> converter.fromToken(bad)); - } - - @Test - void badJson() { - String[] p = JWT_TOKEN.split("\\."); - String notJsonB64url = "bm90LWpzb24"; - String bad = p[0] + "." + notJsonB64url + "." + p[2]; - assertThrows(IllegalArgumentException.class, () -> converter.fromToken(bad)); - } } diff --git a/impl/test/src/test/java/org/openworkflow/sdk/impl/auth/OAuthRequestBuilderTest.java b/impl/test/src/test/java/org/openworkflow/sdk/impl/auth/OAuthRequestBuilderTest.java index 36fa515c8..c5553f69c 100644 --- a/impl/test/src/test/java/org/openworkflow/sdk/impl/auth/OAuthRequestBuilderTest.java +++ b/impl/test/src/test/java/org/openworkflow/sdk/impl/auth/OAuthRequestBuilderTest.java @@ -60,7 +60,6 @@ void resolvesConfiguredEndpointsAndNormalizesLeadingSlash() { @Test void appliesDefaultTokenPathAndLeavesManagementEndpointsUnconfigured() { AuthHttpRequestInfo info = build(baseProperties()); - assertEquals( URI.create("http://localhost:8888/realms/test-realm/oauth2/token"), info.uri().apply(null, null, null)); diff --git a/impl/test/src/test/java/org/openworkflow/sdk/impl/test/OAuthHTTPWorkflowDefinitionTest.java b/impl/test/src/test/java/org/openworkflow/sdk/impl/test/OAuthHTTPWorkflowDefinitionTest.java index 3af637292..ab4199aa5 100644 --- a/impl/test/src/test/java/org/openworkflow/sdk/impl/test/OAuthHTTPWorkflowDefinitionTest.java +++ b/impl/test/src/test/java/org/openworkflow/sdk/impl/test/OAuthHTTPWorkflowDefinitionTest.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Map; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -32,9 +33,12 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.openworkflow.sdk.api.types.Workflow; import org.openworkflow.sdk.impl.WorkflowApplication; +import org.openworkflow.sdk.impl.http.DefaultHttpRequestBuilderFactory; +import org.openworkflow.sdk.impl.http.DefaultURITemplateResolver; import tools.jackson.databind.ObjectMapper; public class OAuthHTTPWorkflowDefinitionTest { @@ -58,20 +62,33 @@ public class OAuthHTTPWorkflowDefinitionTest { } """; - private static WorkflowApplication app; - private MockWebServer authServer; - private MockWebServer apiServer; + private static WorkflowApplication defaultApp; + private static WorkflowApplication jakartaApp; @BeforeAll static void init() { - app = WorkflowApplication.builder().build(); + defaultApp = + WorkflowApplication.builder() + .withHttpRequestBuilderFactory(new DefaultHttpRequestBuilderFactory()) + .withURITemplateResolver(new DefaultURITemplateResolver()) + .build(); + + jakartaApp = WorkflowApplication.builder().build(); } @AfterAll static void cleanup() { - app.close(); + defaultApp.close(); + jakartaApp.close(); } + private static List applications() { + return List.of(defaultApp, jakartaApp); + } + + private MockWebServer authServer; + private MockWebServer apiServer; + @BeforeEach void setUp() throws IOException { authServer = new MockWebServer(); @@ -87,8 +104,10 @@ void tearDown() throws IOException { apiServer.shutdown(); } - @Test - public void testOAuthClientSecretPostPasswordWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthClientSecretPostPasswordWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -129,8 +148,10 @@ public void testOAuthClientSecretPostPasswordWorkflowExecution() throws Exceptio assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthClientSecretPostWithArgsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthClientSecretPostWithArgsWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -179,8 +200,10 @@ public void testOAuthClientSecretPostWithArgsWorkflowExecution() throws Exceptio assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthClientSecretPostWithArgsNoEndPointWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthClientSecretPostWithArgsNoEndPointWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -229,8 +252,10 @@ public void testOAuthClientSecretPostWithArgsNoEndPointWorkflowExecution() throw assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthClientSecretPostWithArgsAllGrantsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthClientSecretPostWithArgsAllGrantsWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -285,8 +310,10 @@ public void testOAuthClientSecretPostWithArgsAllGrantsWorkflowExecution() throws assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthClientSecretPostClientCredentialsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthClientSecretPostClientCredentialsWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -328,9 +355,10 @@ public void testOAuthClientSecretPostClientCredentialsWorkflowExecution() throws assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthClientSecretPostClientCredentialsAllEndpointsWorkflowExecution() - throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthClientSecretPostClientCredentialsAllEndpointsWorkflowExecution( + WorkflowApplication app) throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -372,8 +400,10 @@ public void testOAuthClientSecretPostClientCredentialsAllEndpointsWorkflowExecut assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthClientSecretPostClientCredentialsParamsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthClientSecretPostClientCredentialsParamsWorkflowExecution( + WorkflowApplication app) throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -420,9 +450,10 @@ public void testOAuthClientSecretPostClientCredentialsParamsWorkflowExecution() assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthClientSecretPostClientCredentialsParamsNoEndpointWorkflowExecution() - throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthClientSecretPostClientCredentialsParamsNoEndpointWorkflowExecution( + WorkflowApplication app) throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -469,8 +500,9 @@ public void testOAuthClientSecretPostClientCredentialsParamsNoEndpointWorkflowEx assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthJSONPasswordWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthJSONPasswordWorkflowExecution(WorkflowApplication app) throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -524,8 +556,9 @@ public void testOAuthJSONPasswordWorkflowExecution() throws Exception { assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthJSONWithArgsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthJSONWithArgsWorkflowExecution(WorkflowApplication app) throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -583,8 +616,10 @@ public void testOAuthJSONWithArgsWorkflowExecution() throws Exception { assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthJSONWithArgsNoEndPointWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthJSONWithArgsNoEndPointWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -642,8 +677,10 @@ public void testOAuthJSONWithArgsNoEndPointWorkflowExecution() throws Exception assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthJSONWithArgsAllGrantsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthJSONWithArgsAllGrantsWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -714,8 +751,10 @@ public void testOAuthJSONWithArgsAllGrantsWorkflowExecution() throws Exception { assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthJSONClientCredentialsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthJSONClientCredentialsWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -762,8 +801,10 @@ public void testOAuthJSONClientCredentialsWorkflowExecution() throws Exception { assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthJSONClientCredentialsParamsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthJSONClientCredentialsParamsWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -815,8 +856,10 @@ public void testOAuthJSONClientCredentialsParamsWorkflowExecution() throws Excep assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthJSONClientCredentialsParamsNoEndpointWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthJSONClientCredentialsParamsNoEndpointWorkflowExecution( + WorkflowApplication app) throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -868,12 +911,14 @@ public void testOAuthJSONClientCredentialsParamsNoEndpointWorkflowExecution() th assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOAuthClientSecretJwtClientCredentialsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthClientSecretJwtClientCredentialsWorkflowExecution(WorkflowApplication app) + throws Exception { String assertion = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.client-secret-jwt-assertion.signature"; String tokenRequestBody = runOAuthTokenRequestWorkflow( - "workflows-samples/oauth2/oAuthClientSecretJwtClientCredentialsHttpCall.yaml"); + app, "workflows-samples/oauth2/oAuthClientSecretJwtClientCredentialsHttpCall.yaml"); assertTrue(tokenRequestBody.contains("grant_type=client_credentials")); assertTrue(tokenRequestBody.contains("client_id=serverless-workflow")); @@ -884,12 +929,14 @@ public void testOAuthClientSecretJwtClientCredentialsWorkflowExecution() throws assertFalse(tokenRequestBody.contains("client_secret=")); } - @Test - public void testOAuthPrivateKeyJwtClientCredentialsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthPrivateKeyJwtClientCredentialsWorkflowExecution(WorkflowApplication app) + throws Exception { String assertion = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.private-key-jwt-assertion.signature"; String tokenRequestBody = runOAuthTokenRequestWorkflow( - "workflows-samples/oauth2/oAuthPrivateKeyJwtClientCredentialsHttpCall.yaml"); + app, "workflows-samples/oauth2/oAuthPrivateKeyJwtClientCredentialsHttpCall.yaml"); assertTrue(tokenRequestBody.contains("grant_type=client_credentials")); assertTrue(tokenRequestBody.contains("client_id=serverless-workflow")); @@ -900,11 +947,13 @@ public void testOAuthPrivateKeyJwtClientCredentialsWorkflowExecution() throws Ex assertFalse(tokenRequestBody.contains("client_secret=")); } - @Test - public void testOAuthTokenExchangeSubjectActorWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOAuthTokenExchangeSubjectActorWorkflowExecution(WorkflowApplication app) + throws Exception { String tokenRequestBody = runOAuthTokenRequestWorkflow( - "workflows-samples/oauth2/oAuthClientSecretPostTokenExchangeHttpCall.yaml"); + app, "workflows-samples/oauth2/oAuthClientSecretPostTokenExchangeHttpCall.yaml"); assertTrue( tokenRequestBody.contains("grant_type=urn:ietf:params:oauth:grant-type:token-exchange")); @@ -918,7 +967,8 @@ public void testOAuthTokenExchangeSubjectActorWorkflowExecution() throws Excepti "actor_token_type=urn:ietf:params:oauth:token-type:access_token")); } - private String runOAuthTokenRequestWorkflow(String workflowResource) throws Exception { + private String runOAuthTokenRequestWorkflow(WorkflowApplication app, String workflowResource) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); diff --git a/impl/test/src/test/java/org/openworkflow/sdk/impl/test/OpenIDCHTTPWorkflowDefinitionTest.java b/impl/test/src/test/java/org/openworkflow/sdk/impl/test/OpenIDCHTTPWorkflowDefinitionTest.java index 663486aaf..f8803a34b 100644 --- a/impl/test/src/test/java/org/openworkflow/sdk/impl/test/OpenIDCHTTPWorkflowDefinitionTest.java +++ b/impl/test/src/test/java/org/openworkflow/sdk/impl/test/OpenIDCHTTPWorkflowDefinitionTest.java @@ -15,12 +15,14 @@ */ package org.openworkflow.sdk.impl.test; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.openworkflow.sdk.api.WorkflowReader.readWorkflowFromClasspath; import static org.openworkflow.sdk.impl.test.AccessTokenProvider.fakeAccessToken; import java.io.IOException; +import java.util.List; import java.util.Map; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -29,15 +31,21 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.openworkflow.sdk.api.types.Workflow; import org.openworkflow.sdk.impl.WorkflowApplication; +import org.openworkflow.sdk.impl.http.DefaultHttpRequestBuilderFactory; +import org.openworkflow.sdk.impl.http.DefaultURITemplateResolver; import tools.jackson.databind.ObjectMapper; public class OpenIDCHTTPWorkflowDefinitionTest { private static final ObjectMapper MAPPER = new ObjectMapper(); + private static WorkflowApplication defaultApp; + private static WorkflowApplication jakartaApp; + private static final String RESPONSE = """ { @@ -55,20 +63,30 @@ public class OpenIDCHTTPWorkflowDefinitionTest { } """; - private static WorkflowApplication app; - private MockWebServer authServer; - private MockWebServer apiServer; - @BeforeAll static void init() { - app = WorkflowApplication.builder().build(); + defaultApp = + WorkflowApplication.builder() + .withHttpRequestBuilderFactory(new DefaultHttpRequestBuilderFactory()) + .withURITemplateResolver(new DefaultURITemplateResolver()) + .build(); + + jakartaApp = WorkflowApplication.builder().build(); } @AfterAll static void cleanup() { - app.close(); + defaultApp.close(); + jakartaApp.close(); } + private static List applications() { + return List.of(defaultApp, jakartaApp); + } + + private MockWebServer authServer; + private MockWebServer apiServer; + @BeforeEach void setUp() throws IOException { authServer = new MockWebServer(); @@ -84,8 +102,10 @@ void tearDown() throws IOException { apiServer.shutdown(); } - @Test - public void testOpenIDCClientSecretPostPasswordWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOpenIDCClientSecretPostPasswordWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -130,8 +150,10 @@ public void testOpenIDCClientSecretPostPasswordWorkflowExecution() throws Except assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOpenIDCClientSecretPostWithArgsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOpenIDCClientSecretPostWithArgsWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -181,8 +203,10 @@ public void testOpenIDCClientSecretPostWithArgsWorkflowExecution() throws Except assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOpenIDCClientSecretPostWithArgsAllGrantsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOpenIDCClientSecretPostWithArgsAllGrantsWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -224,9 +248,8 @@ public void testOpenIDCClientSecretPostWithArgsAllGrantsWorkflowExecution() thro assertTrue(tokenRequestBody.contains("username=serverless-workflow-test")); assertTrue(tokenRequestBody.contains("password=serverless-workflow-test")); - assertTrue( - tokenRequestBody.contains( - "scope=pets%3Aread+pets%3Awrite+pets%3Adelete+pets%3Acreate+openid")); + assertThat(tokenRequestBody) + .contains("scope=pets%3Aread+pets%3Awrite+pets%3Adelete+pets%3Acreate+openid"); assertTrue( tokenRequestBody.contains("audience=serverless-workflow+another-audience+third-audience")); @@ -238,9 +261,10 @@ public void testOpenIDCClientSecretPostWithArgsAllGrantsWorkflowExecution() thro System.out.println("tokenRequestBody = \n" + tokenRequestBody); } - @Test - public void testOpenIDCClientSecretPostClientCredentialsParamsWorkflowExecution() - throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOpenIDCClientSecretPostClientCredentialsParamsWorkflowExecution( + WorkflowApplication app) throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -287,9 +311,10 @@ public void testOpenIDCClientSecretPostClientCredentialsParamsWorkflowExecution( assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOpenIDCClientSecretPostClientCredentialsParamsNoEndpointWorkflowExecution() - throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOpenIDCClientSecretPostClientCredentialsParamsNoEndpointWorkflowExecution( + WorkflowApplication app) throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -335,8 +360,9 @@ public void testOpenIDCClientSecretPostClientCredentialsParamsNoEndpointWorkflow assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOpenIDCJSONPasswordWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOpenIDCJSONPasswordWorkflowExecution(WorkflowApplication app) throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -390,8 +416,9 @@ public void testOpenIDCJSONPasswordWorkflowExecution() throws Exception { assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOpenIDCJSONWithArgsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOpenIDCJSONWithArgsWorkflowExecution(WorkflowApplication app) throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -449,8 +476,10 @@ public void testOpenIDCJSONWithArgsWorkflowExecution() throws Exception { assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOpenIDCJSONWithArgsNoEndPointWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOpenIDCJSONWithArgsNoEndPointWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -508,8 +537,10 @@ public void testOpenIDCJSONWithArgsNoEndPointWorkflowExecution() throws Exceptio assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOpenIDCJSONWithArgsAllGrantsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOpenIDCJSONWithArgsAllGrantsWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -580,8 +611,10 @@ public void testOpenIDCJSONWithArgsAllGrantsWorkflowExecution() throws Exception assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOpenIDCJSONClientCredentialsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOpenIDCJSONClientCredentialsWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -628,8 +661,10 @@ public void testOpenIDCJSONClientCredentialsWorkflowExecution() throws Exception assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOpenIDCJSONClientCredentialsParamsWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOpenIDCJSONClientCredentialsParamsWorkflowExecution(WorkflowApplication app) + throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); @@ -681,8 +716,10 @@ public void testOpenIDCJSONClientCredentialsParamsWorkflowExecution() throws Exc assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } - @Test - public void testOpenIDCJSONClientCredentialsParamsNoEndpointWorkflowExecution() throws Exception { + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("applications") + public void testOpenIDCJSONClientCredentialsParamsNoEndpointWorkflowExecution( + WorkflowApplication app) throws Exception { String jwt = fakeAccessToken(); String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); From 82d81a0cd72a541c86de17e247393e4bdd775591 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Wed, 11 Nov 2026 18:23:21 +0100 Subject: [PATCH 2/2] [Fix #1805] Copilot comments Signed-off-by: Francisco Javier Tirado Sarti --- impl/README.md | 40 ++++------ impl/core/pom.xml | 15 ++++ .../openworkflow/sdk/impl/WorkflowUtils.java | 2 +- .../impl/auth/AbstractAuthRequestBuilder.java | 7 +- .../sdk/impl/auth/AccessTokenProvider.java | 76 ++++++++++--------- .../sdk/impl/auth/AuthHttpRequestInfo.java | 4 +- .../impl/auth/AuthHttpRequestInfoBuilder.java | 24 ++++-- .../sdk/impl/auth/CommonOAuthProvider.java | 12 +-- .../sdk/impl/auth/JWTConverter.java | 4 +- .../executors/openapi/OpenAPIExecutor.java | 20 +++-- .../openapi/OpenAPIExecutorBuilder.java | 0 .../executors/openapi/OpenAPIProcessor.java | 0 .../openapi/OperationDefinition.java | 0 .../openapi/OperationPathResolver.java | 29 +++++-- .../openapi/ParameterDefinition.java | 0 .../executors/openapi/UnifiedOpenAPI.java | 0 .../openapi/UnifiedOpenAPIReader.java | 0 .../sdk/impl/http/DefaultHttpRequest.java | 36 +++++---- .../impl/http/DefaultURITemplateResolver.java | 9 +-- .../sdk/impl/http/URIDecodedBuilder.java | 6 -- .../sdk/impl/http/URITemplateResolver.java | 7 +- .../impl/http/WorkflowHttpContentType.java | 24 ++++-- .../impl/http/WorkflowHttpRequestHelper.java | 28 +++---- .../impl/http/WorkflowHttpRequestInfo.java | 43 +++++------ ...low.sdk.impl.executors.CallableTaskBuilder | 3 +- .../openapi/OpenAPIProcessorTest.java | 0 .../http/DefaultURITemplateResolverTest.java | 44 +++++++++++ .../resources/schema/openapi/petstore.json | 0 .../resources/schema/swagger/petstore.json | 0 impl/{jwt-impl => http-jackson}/pom.xml | 4 +- .../jackson/JacksonModelConverterFactory.java | 0 ...l.additional.NamedSuppliedAdditionalObject | 0 .../jakarta/http/JakartaRequestExecutor.java | 25 +++--- .../template/JaxrsURITemplateResolver.java | 12 +-- impl/jackson/pom.xml | 4 + ...rg.openworkflow.sdk.impl.auth.JWTConverter | 1 - .../oauth/jackson/JacksonJWTConverter.java | 3 +- ...rg.openworkflow.sdk.impl.auth.JWTConverter | 1 + .../auth/jwt/jackson}/JacksonJWTImplTest.java | 47 ++++++++++-- impl/openapi-jackson/pom.xml | 4 +- impl/openapi/pom.xml | 50 ------------ ...low.sdk.impl.executors.CallableTaskBuilder | 1 - impl/pom.xml | 12 +-- impl/test/pom.xml | 2 +- .../impl/auth/OAuthRequestBuilderTest.java | 1 + .../test/ActiveInstanceRegistrationTest.java | 1 - 46 files changed, 337 insertions(+), 264 deletions(-) rename impl/{openapi => core}/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIExecutor.java (92%) rename impl/{openapi => core}/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIExecutorBuilder.java (100%) rename impl/{openapi => core}/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIProcessor.java (100%) rename impl/{openapi => core}/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OperationDefinition.java (100%) rename impl/{openapi => core}/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OperationPathResolver.java (59%) rename impl/{openapi => core}/src/main/java/org/openworkflow/sdk/impl/executors/openapi/ParameterDefinition.java (100%) rename impl/{openapi => core}/src/main/java/org/openworkflow/sdk/impl/executors/openapi/UnifiedOpenAPI.java (100%) rename impl/{openapi => core}/src/main/java/org/openworkflow/sdk/impl/executors/openapi/UnifiedOpenAPIReader.java (100%) rename impl/{openapi => core}/src/test/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIProcessorTest.java (100%) create mode 100644 impl/core/src/test/java/org/openworkflow/sdk/impl/http/DefaultURITemplateResolverTest.java rename impl/{openapi => core}/src/test/resources/schema/openapi/petstore.json (100%) rename impl/{openapi => core}/src/test/resources/schema/swagger/petstore.json (100%) rename impl/{jwt-impl => http-jackson}/pom.xml (90%) rename impl/{jwt-impl => http-jackson}/src/main/java/org/openworkflow/sdk/impl/executors/http/jackson/JacksonModelConverterFactory.java (100%) rename impl/{jwt-impl => http-jackson}/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.additional.NamedSuppliedAdditionalObject (100%) delete mode 100644 impl/jwt-impl/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.auth.JWTConverter rename impl/{jwt-impl/src/main/java/org/openworkflow/sdk/impl/executors/http => model/src/main/java/org/openworkflow/sdk/impl}/oauth/jackson/JacksonJWTConverter.java (97%) create mode 100644 impl/model/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.auth.JWTConverter rename impl/{jwt-impl/src/test/java/org/openworkflow/sdk/impl/http/jwt => model/src/test/java/org/openworkflow/sdk/impl/auth/jwt/jackson}/JacksonJWTImplTest.java (74%) delete mode 100644 impl/openapi/pom.xml delete mode 100644 impl/openapi/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.executors.CallableTaskBuilder diff --git a/impl/README.md b/impl/README.md index 01dee15b8..1468c9bba 100644 --- a/impl/README.md +++ b/impl/README.md @@ -85,23 +85,24 @@ This SDK is modular by design—pull in only what you need: * **openworkflow-impl-jackson** 👉 **Most users add this one.** Contains implementation of core features using Jackson library, like JQ expressions, JSON Schema validation, and CloudEvents (de)serialization. Internally, it is an aggregation of smaller modules: + * **openworkflow-impl-jq** JQ expression implementation + * **openworkflow-impl-model** Jackson model implementation and Jackson JWT converter for oauth2 calls. + * **openworkflow-impl-validation** Schema validation implementation + * **openworkflow-impl-lifecycle-events** Enable publication of lifecycle events as Json cloud events. + * **openworkflow-impl-function** Jackson Function support. + * **openworkflow-impl-json** Jackson common utilities shared by all modules in this list + * **openworkflow-impl-cron** Cron parsing for workflow scheduled execution + * **openworkflow-impl-openapi** Deserialization support of OpenAPI schemas using Jackson (required for OpenAPI call task to work) - * **openworkflow-impl-jq** JQ expression implementation - * **openworkflow-impl-model** Jackson model implementation - * **openworkflow-impl-validation** Schema validation implementation - * **openworkflow-impl-lifecycle-events** Enable publication of lifecycle events as Json cloud events. - * **openworkflow-impl-function** Jackson Function support. - * **openworkflow-impl-json** Jackson common utilities shared by all modules in this list - * **openworkflow-impl-cron** Cron parsing for workflow scheduled execution - Additionally, The OpenWokflow specification defines a set of [call task](https://github.com/open-workflow-specification/specification/blob/main/dsl-reference.md#call), which requires additional dependencies, with the exception of HTTP (see section below). You might add those ones needed depending on the feature you are using in your definition: +Additionally, The OpenWokflow specification defines a set of [call task](https://github.com/open-workflow-specification/specification/blob/main/dsl-reference.md#call), which requires additional dependencies, with the exception of HTTP (see section below). You might add those ones needed depending on the feature you are using in your definition: + - * **openworkflow-impl-openapi** OpenAPI support using Jackson - * **openworkflow-impl-openapi-base** Base OpenAPI support intended to be extended for a specific JSON library * **openworkflow-impl-grpc** gRPC support * **openworkflow-impl-script-python** Python support * **openworkflow-impl-script-js** Javascript support - * **openworkflow-impl-container** Docker container support + * **openworkflow-impl-container** Docker container support + * **openworkflow-impl-a2a** A2A protocol support. There are also companion modules/docs for: @@ -111,12 +112,12 @@ There are also companion modules/docs for: ### Http dependencies -By default, the SDK reference implementation use `java.net.HttpRequest` to support Http calls. Therefore, no dependency needs to be added for using Http Call task or downloading of http resources, like OpenAPI schemas. +By default, the SDK reference implementation use `java.net.HttpRequest` to support Http/OpenAPI calls. Therefore, no dependency (besides the jackson modules described above) needs to be added for using Http Call task or downloading http resources. However, for further configuration of Http functionality, users might include the Jakarta based modules, which will allow customizing `jakarta.ws.rs.client.Client` in Jakarta EE environments. * **openworkflow-impl-http** HTTP `Call` task handler, based on JAX-RS client - * **openworkflow-impl-jackson-jwt** Optimized entity converter for Jackson model and OAuth2/OIDC helpers for Jakarta HTTP calls. + * **openworkflow-impl-http-jackson** Optimized entity converter for Jackson model --- @@ -131,25 +132,12 @@ However, for further configuration of Http functionality, users might include th openworkflow-impl-jackson - - - org.openworkflow.sdk - openworkflow-impl-http - - - - - org.openworkflow.sdk - openworkflow-impl-jackson-jwt - ``` ### Gradle (Kotlin/Groovy) ```gradle implementation("org.openworkflow.sdk:openworkflow-impl-jackson") -implementation("org.openworkflow.sdk:openworkflow-impl-http") // if using HTTP -implementation("org.openworkflow.sdk:openworkflow-impl-jackson-jwt") // if using OAuth2/OIDC ``` > Requires **Java 21+**. diff --git a/impl/core/pom.xml b/impl/core/pom.xml index 1001d74f7..a6fc68270 100644 --- a/impl/core/pom.xml +++ b/impl/core/pom.xml @@ -40,4 +40,19 @@ test + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/WorkflowUtils.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/WorkflowUtils.java index 412e6feac..e319f09fa 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/WorkflowUtils.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/WorkflowUtils.java @@ -319,7 +319,7 @@ public static WorkflowValueResolver getURISupplier( return (w, t, n) -> application .templateResolver() - .resolveTemplates(template.getLiteralUriTemplate(), w, t, n); + .resolveTemplates(template.getLiteralUriTemplate(), n.asMap().orElse(Map.of())); } throw new IllegalArgumentException("Invalid uritemplate definition " + template); } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AbstractAuthRequestBuilder.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AbstractAuthRequestBuilder.java index 4e3e55fe1..bc2e7c8af 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AbstractAuthRequestBuilder.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AbstractAuthRequestBuilder.java @@ -55,6 +55,7 @@ public AbstractAuthRequestBuilder(WorkflowApplication application) { @Override public AuthHttpRequestInfo apply(T authenticationData) { + requestBuilder.withIssuers(authenticationData.getIssuers()); requestEncoding(authenticationData); authenticationURI(authenticationData); audience(authenticationData); @@ -66,6 +67,7 @@ public AuthHttpRequestInfo apply(T authenticationData) { @Override public AuthHttpRequestInfo apply(Map secret) { + requestBuilder.withIssuers((String) secret.get("issuers")); requestEncoding(secret); authenticationURI(secret); audience(secret); @@ -194,10 +196,7 @@ void requestEncoding(T authenticationData) { void requestEncoding(Map secret) { Map request = (Map) secret.get(REQUEST); - String encoding = (String) request.get(ENCODING); - if (isValid(encoding)) { - requestBuilder.addHeader("Content-Type", encoding); - } + requestBuilder.withContentType((String) request.get(ENCODING)); } protected abstract void authenticationURI(T authenticationData); diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AccessTokenProvider.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AccessTokenProvider.java index 08bae24b6..dac605d9d 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AccessTokenProvider.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AccessTokenProvider.java @@ -16,9 +16,11 @@ package org.openworkflow.sdk.impl.auth; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; import org.openworkflow.sdk.impl.TaskContext; import org.openworkflow.sdk.impl.WorkflowApplication; import org.openworkflow.sdk.impl.WorkflowContext; @@ -29,54 +31,58 @@ class AccessTokenProvider { private final WorkflowApplication app; - private final List issuers; - private final AuthHttpRequestInfo requestInfo; + private final Supplier requestInfoSupplier; private final JWTConverter jwtConverter; AccessTokenProvider( WorkflowApplication app, - AuthHttpRequestInfo requestInfo, - List issuers, - JWTConverter converter) { + Supplier requestInfoSupplier, + JWTConverter jwtConverter) { this.app = app; - this.requestInfo = requestInfo; - this.issuers = issuers; - this.jwtConverter = converter; + this.requestInfoSupplier = requestInfoSupplier; + this.jwtConverter = jwtConverter; } public CompletableFuture validateAndGet(WorkflowContext workflow, TaskContext context) { - WorkflowHttpRequestInfo httpRequestInfo = buildHttpRequestInfo(workflow, context); - return WorkflowHttpRequestHelper.executeRequest(httpRequestInfo, app, workflow, context) - .thenApply( - bytes -> { - JWT jwt = jwtConverter.fromToken(bytes); - if (issuers != null && !issuers.isEmpty()) { - - jwt.issuer() - .ifPresent( - issuer -> { - if (!issuers.contains(issuer)) { - throw new IllegalStateException("Token issuer is not valid: " + issuer); - } - }); - } - return jwt; - }); + AuthHttpRequestInfo authRequestInfo = requestInfoSupplier.get(); + WorkflowHttpRequestInfo httpRequestInfo = + buildHttpRequestInfo(workflow, context, authRequestInfo); + CompletableFuture future = + WorkflowHttpRequestHelper.executeRequest(httpRequestInfo, app, workflow, context) + .thenApply(bytes -> jwtConverter.fromToken(bytes)); + List issuers = authRequestInfo.issuers(); + if (!issuers.isEmpty()) { + future = + future.whenComplete( + (jwt, ex) -> { + if (jwt != null) { + jwt.issuer() + .ifPresent( + issuer -> { + if (!issuers.contains(issuer)) { + throw new IllegalStateException( + "Token issuer is not valid: " + issuer); + } + }); + } + }); + } + return future; } - private WorkflowHttpRequestInfo buildHttpRequestInfo(WorkflowContext workflow, TaskContext task) { + private WorkflowHttpRequestInfo buildHttpRequestInfo( + WorkflowContext workflow, TaskContext task, AuthHttpRequestInfo requestInfo) { WorkflowHttpRequestInfo.Builder builder = WorkflowHttpRequestInfo.builder("POST"); - builder.header("grant_type", requestInfo.grantType()); - builder.header("User-Agent", "OAuth2-Client-Credentials/1.0"); - builder.header("Cache-Control", "no-cache"); - builder.header("Accept", "application/json"); + Map headers = new LinkedHashMap<>(); + headers.put("grant_type", requestInfo.grantType()); + headers.put("User-Agent", "OAuth2-Client-Credentials/1.0"); + headers.put("Cache-Control", "no-cache"); + headers.put("Accept", "application/json"); for (var entry : requestInfo.headers().entrySet()) { - String headerValue = entry.getValue().apply(workflow, task); - if (headerValue != null) { - builder.header(entry.getKey(), headerValue); - } + headers.put(entry.getKey(), entry.getValue().apply(workflow, task)); } - builder.header(WorkflowHttpContentType.HEADER_NAME, requestInfo.contentType().toString()); + headers.put(WorkflowHttpContentType.HEADER_NAME, requestInfo.contentType().toString()); + builder.headers(headers); Map body = new HashMap(); body.put("grant_type", requestInfo.grantType()); requestInfo.queryParams().forEach((key, value) -> body.put(key, value.apply(workflow, task))); diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfo.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfo.java index 2732d4a0b..45314f7a0 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfo.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfo.java @@ -16,6 +16,7 @@ package org.openworkflow.sdk.impl.auth; import java.net.URI; +import java.util.List; import java.util.Map; import java.util.Optional; import org.openworkflow.sdk.impl.WorkflowValueResolver; @@ -29,4 +30,5 @@ public record AuthHttpRequestInfo( Optional> revocationUri, Optional> introspectionUri, String grantType, - WorkflowHttpContentType contentType) {} + WorkflowHttpContentType contentType, + List issuers) {} diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfoBuilder.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfoBuilder.java index 37912b5f6..8e50d70ec 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfoBuilder.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/AuthHttpRequestInfoBuilder.java @@ -16,7 +16,9 @@ package org.openworkflow.sdk.impl.auth; import java.net.URI; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -42,6 +44,8 @@ class AuthHttpRequestInfoBuilder { private WorkflowHttpContentType contentType; + private List issuers; + AuthHttpRequestInfoBuilder() { headers = new HashMap<>(); queryParams = new HashMap<>(); @@ -88,6 +92,16 @@ AuthHttpRequestInfoBuilder withRevocationUri(Optional return this; } + AuthHttpRequestInfoBuilder withIssuers(List issuers) { + this.issuers = issuers; + return this; + } + + AuthHttpRequestInfoBuilder withIssuers(String issuers) { + this.issuers = issuers == null ? null : Arrays.asList(issuers.split(",")); + return this; + } + AuthHttpRequestInfoBuilder withIntrospectionUri( Optional> introspectionUri) { this.introspectionUri = introspectionUri; @@ -102,7 +116,7 @@ AuthHttpRequestInfoBuilder withContentType(OAuth2TokenRequest oAuth2TokenRequest } AuthHttpRequestInfoBuilder withContentType(String contentType) { - this.contentType = WorkflowHttpContentType.fromValue(contentType).orElse(null); + this.contentType = WorkflowHttpContentType.fromValue(contentType); return this; } @@ -114,9 +128,6 @@ AuthHttpRequestInfoBuilder withGrantType(String grantType) { AuthHttpRequestInfo build() { Objects.requireNonNull(uri, "URI must be set before building the request"); Objects.requireNonNull(grantType, "Grant type must be set before building the request"); - if (contentType == null) { - contentType = WorkflowHttpContentType.APPLICATION_X_WWW_FORM_URLENCODED; - } return new AuthHttpRequestInfo( headers, queryParams, @@ -125,6 +136,9 @@ AuthHttpRequestInfo build() { revocationUri, introspectionUri, grantType, - contentType); + contentType == null + ? WorkflowHttpContentType.APPLICATION_X_WWW_FORM_URLENCODED + : contentType, + issuers == null ? List.of() : issuers); } } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/CommonOAuthProvider.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/CommonOAuthProvider.java index c3329158e..fce2cd510 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/CommonOAuthProvider.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/CommonOAuthProvider.java @@ -19,7 +19,6 @@ import static org.openworkflow.sdk.impl.WorkflowUtils.secret; import java.net.URI; -import java.util.Arrays; import java.util.Map; import java.util.concurrent.CompletableFuture; import org.openworkflow.sdk.api.types.OAuth2AuthenticationData; @@ -71,21 +70,16 @@ private static AccessTokenProvider build( OAuth2AuthenticationData authenticationData, AuthRequestBuilder authBuilder, WorkflowApplication app) { + final AuthHttpRequestInfo requestInfo = authBuilder.apply((T) authenticationData); return new AccessTokenProvider( - app, - authBuilder.apply((T) authenticationData), - authenticationData.getIssuers(), - app.serviceLoadedClass(JWTConverter.class)); + app, () -> requestInfo, app.serviceLoadedClass(JWTConverter.class)); } private static AccessTokenProvider build( String secretName, AuthRequestBuilder authBuilder, WorkflowApplication app) { - Map secret = secret(app, secretName); - String issuers = (String) secret.get("issuers"); return new AccessTokenProvider( app, - authBuilder.apply(secret), - issuers != null ? Arrays.asList(issuers.split(",")) : null, + () -> authBuilder.apply(secret(app, secretName)), app.serviceLoadedClass(JWTConverter.class)); } } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/JWTConverter.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/JWTConverter.java index 9d927673e..be59aa66c 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/JWTConverter.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/auth/JWTConverter.java @@ -20,9 +20,9 @@ public interface JWTConverter extends ServicePriority { /** - * Converts a JWT token string into a JWT object. + * Converts an Oauth2 Http response into a JWT object. * - * @param token the JWT token string + * @param token the oauth2 response as byte[] * @return a JWT object containing the token and its claims */ JWT fromToken(byte[] token) throws IllegalArgumentException; diff --git a/impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIExecutor.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIExecutor.java similarity index 92% rename from impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIExecutor.java rename to impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIExecutor.java index d49d154a0..5f10044da 100644 --- a/impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIExecutor.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIExecutor.java @@ -93,19 +93,20 @@ private void fillHttpBuilder(WorkflowApplication application, OperationDefinitio Map headers = new HashMap<>(); Map queryMap = new HashMap<>(); Map pathParameters = new HashMap<>(); + Set mandatoryParameters = new HashSet<>(); Set missingParams = new HashSet<>(); boolean hasBody = operation.getOperation().hasRequestBody(); Map bodyParameters = new HashMap<>(parameters); for (ParameterDefinition parameter : operation.getParameters()) { switch (parameter.in()) { case "header": - param(parameter, bodyParameters, headers, missingParams); + param(parameter, bodyParameters, headers, missingParams, mandatoryParameters); break; case "path": - param(parameter, bodyParameters, pathParameters, missingParams); + param(parameter, bodyParameters, pathParameters, missingParams, mandatoryParameters); break; case "query": - param(parameter, bodyParameters, queryMap, missingParams); + param(parameter, bodyParameters, queryMap, missingParams, mandatoryParameters); break; case "body": hasBody = true; @@ -125,18 +126,25 @@ private void fillHttpBuilder(WorkflowApplication application, OperationDefinitio .withQueryMap(!queryMap.isEmpty() ? queryMap : null) .withHeaders(!headers.isEmpty() ? headers : null) .withMethod(operation.getMethod()) - .withPath(new OperationPathResolver(operation.getPath(), application, pathParameters)); + .withPath( + new OperationPathResolver( + operation.getPath(), application, pathParameters, mandatoryParameters)); } private void param( ParameterDefinition parameter, Map origMap, Map collectorMap, - Set missingParams) { + Set missingParams, + Set mandatoryParams) { String name = parameter.name(); + boolean isRequired = parameter.required(); + if (isRequired) { + mandatoryParams.add(name); + } if (origMap.containsKey(name)) { collectorMap.put(parameter.name(), origMap.remove(name)); - } else if (parameter.required()) { + } else if (isRequired) { UnifiedOpenAPI.Schema schema = parameter.schema(); Object defaultValue = schema != null ? schema._default() : null; if (defaultValue != null) { diff --git a/impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIExecutorBuilder.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIExecutorBuilder.java similarity index 100% rename from impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIExecutorBuilder.java rename to impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIExecutorBuilder.java diff --git a/impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIProcessor.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIProcessor.java similarity index 100% rename from impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIProcessor.java rename to impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIProcessor.java diff --git a/impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OperationDefinition.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OperationDefinition.java similarity index 100% rename from impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OperationDefinition.java rename to impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OperationDefinition.java diff --git a/impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OperationPathResolver.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OperationPathResolver.java similarity index 59% rename from impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OperationPathResolver.java rename to impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OperationPathResolver.java index 57a3a5113..33fbb2277 100644 --- a/impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OperationPathResolver.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/OperationPathResolver.java @@ -15,9 +15,11 @@ */ package org.openworkflow.sdk.impl.executors.openapi; -import jakarta.ws.rs.core.UriBuilder; import java.net.URI; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import org.openworkflow.sdk.impl.TaskContext; import org.openworkflow.sdk.impl.WorkflowApplication; import org.openworkflow.sdk.impl.WorkflowContext; @@ -28,16 +30,33 @@ class OperationPathResolver implements WorkflowValueResolver { private final String path; private final WorkflowValueResolver> asMap; + private final WorkflowApplication app; + private final Set mandatoryParameters; - OperationPathResolver(String path, WorkflowApplication application, Map args) { + OperationPathResolver( + String path, + WorkflowApplication application, + Map args, + Set mandatoryParameters) { this.path = path; + this.app = application; this.asMap = WorkflowUtils.buildMapResolver(application, args); + this.mandatoryParameters = mandatoryParameters; } @Override public URI apply(WorkflowContext workflow, TaskContext task, WorkflowModel model) { - return UriBuilder.fromUri(path) - .resolveTemplates(asMap.apply(workflow, task, model), false) - .build(); + Map map = asMap.apply(workflow, task, model); + List nullKeys = + map.entrySet().stream() + .filter(e -> e.getValue() == null && mandatoryParameters.contains(e.getKey())) + .map(Entry::getKey) + .toList(); + if (!nullKeys.isEmpty()) { + throw new IllegalArgumentException( + "Following mandatory OpenAPI path parameters has null values after expression resolution " + + nullKeys); + } + return app.templateResolver().resolveTemplates(path, map); } } diff --git a/impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/ParameterDefinition.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/ParameterDefinition.java similarity index 100% rename from impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/ParameterDefinition.java rename to impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/ParameterDefinition.java diff --git a/impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/UnifiedOpenAPI.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/UnifiedOpenAPI.java similarity index 100% rename from impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/UnifiedOpenAPI.java rename to impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/UnifiedOpenAPI.java diff --git a/impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/UnifiedOpenAPIReader.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/UnifiedOpenAPIReader.java similarity index 100% rename from impl/openapi/src/main/java/org/openworkflow/sdk/impl/executors/openapi/UnifiedOpenAPIReader.java rename to impl/core/src/main/java/org/openworkflow/sdk/impl/executors/openapi/UnifiedOpenAPIReader.java diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/DefaultHttpRequest.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/DefaultHttpRequest.java index 50e2d7588..a84cf0d5a 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/DefaultHttpRequest.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/DefaultHttpRequest.java @@ -16,13 +16,16 @@ package org.openworkflow.sdk.impl.http; import java.net.URI; +import java.net.URLEncoder; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublisher; import java.net.http.HttpRequest.Builder; import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.function.Function; import java.util.stream.Collectors; import org.openworkflow.sdk.impl.TaskContext; import org.openworkflow.sdk.impl.WorkflowApplication; @@ -64,18 +67,16 @@ public CompletableFuture invoke( return WorkflowHttpRequestHelper.processAuth( httpInfo, builder::header, workflowContext, taskContext) .thenCompose( - __ -> - client - .sendAsync(builder.build(), HttpResponse.BodyHandlers.ofByteArray()) - .exceptionallyCompose( - ex -> - CompletableFuture.failedFuture( - WorkflowHttpRequestHelper.fromError(ex, taskContext)))) + __ -> client.sendAsync(builder.build(), HttpResponse.BodyHandlers.ofByteArray())) + .exceptionallyCompose( + ex -> + CompletableFuture.failedFuture( + WorkflowHttpRequestHelper.fromError(ex, taskContext))) .thenApply(response -> new DefaultHttpResponse(app, taskContext, response)); } private URI addQueryParams(URI uri, Map queryParams) { - String addedQuery = encodeMap(queryParams); + String addedQuery = encodeMap(queryParams, DefaultHttpRequest::encodeQuery); String rawQuery = uri.getRawQuery(); return new URIDecodedBuilder(uri) .query(rawQuery == null || rawQuery.isEmpty() ? addedQuery : rawQuery + "&" + addedQuery) @@ -104,17 +105,22 @@ private BodyPublisher buildBody() { .orElseThrow( () -> new IllegalStateException( - "Underlying model needs to support asMap in order to use form urlencoded content type")))); + "Underlying model needs to support asMap in order to use form urlencoded content type")), + DefaultHttpRequest::encodeForm)); }; } - private String encodeMap(Map map) { + private String encodeMap(Map map, Function decoder) { return map.entrySet().stream() - .map( - entry -> - URIDecodedBuilder.encode(entry.getKey()) - + "=" - + URIDecodedBuilder.encode(entry.getValue())) + .map(entry -> decoder.apply(entry.getKey()) + "=" + decoder.apply(entry.getValue())) .collect(Collectors.joining("&")); } + + public static String encodeForm(Object part) { + return part == null ? "" : URLEncoder.encode(part.toString(), StandardCharsets.UTF_8); + } + + public static String encodeQuery(Object part) { + return encodeForm(part).replace("+", "%20"); + } } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/DefaultURITemplateResolver.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/DefaultURITemplateResolver.java index 5d2f95f8b..6d9373ee4 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/DefaultURITemplateResolver.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/DefaultURITemplateResolver.java @@ -24,16 +24,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.openworkflow.sdk.impl.TaskContext; -import org.openworkflow.sdk.impl.WorkflowContext; -import org.openworkflow.sdk.impl.WorkflowModel; public class DefaultURITemplateResolver implements URITemplateResolver { @Override - public URI resolveTemplates( - String uri, WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel model) { - return URI.create(expandImpl(uri, model.asMap().orElse(Map.of()))); + public URI resolveTemplates(String uri, Map map) { + return URI.create(expandImpl(uri, map)); } // Private implementation @@ -89,7 +85,6 @@ private static void validateLiteral(Character c, int col) { case '*': case ':': case '~': - case '-': throw new IllegalArgumentException( "Illegal character identified in the token at col:" + col); default: diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/URIDecodedBuilder.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/URIDecodedBuilder.java index 5670c3b95..b7693939a 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/URIDecodedBuilder.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/URIDecodedBuilder.java @@ -16,8 +16,6 @@ package org.openworkflow.sdk.impl.http; import java.net.URI; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; public class URIDecodedBuilder { @@ -81,8 +79,4 @@ public URI build() { return URI.create(sb.toString()); } - - public static String encode(Object part) { - return URLEncoder.encode(part.toString(), StandardCharsets.UTF_8); - } } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/URITemplateResolver.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/URITemplateResolver.java index 383e4bba0..e26220d90 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/URITemplateResolver.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/URITemplateResolver.java @@ -16,12 +16,9 @@ package org.openworkflow.sdk.impl.http; import java.net.URI; +import java.util.Map; import org.openworkflow.sdk.impl.ServicePriority; -import org.openworkflow.sdk.impl.TaskContext; -import org.openworkflow.sdk.impl.WorkflowContext; -import org.openworkflow.sdk.impl.WorkflowModel; public interface URITemplateResolver extends ServicePriority { - URI resolveTemplates( - String uri, WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel model); + URI resolveTemplates(String uri, Map map); } diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpContentType.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpContentType.java index ae7bd9a8c..aae95d4f3 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpContentType.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpContentType.java @@ -15,6 +15,7 @@ */ package org.openworkflow.sdk.impl.http; +import java.util.Map; import java.util.Optional; import org.openworkflow.sdk.api.types.OAuth2TokenRequest.Oauth2TokenRequestEncoding; @@ -22,7 +23,7 @@ public enum WorkflowHttpContentType { APPLICATION_JSON("application/json"), APPLICATION_X_WWW_FORM_URLENCODED("application/x-www-form-urlencoded"); - public static final String HEADER_NAME = "content-type"; + public static final String HEADER_NAME = "Content-Type"; private final String value; @@ -39,13 +40,26 @@ public static WorkflowHttpContentType fromValue(Oauth2TokenRequestEncoding encod }; } - public static Optional fromValue(String value) { + public static Optional fromHeaders(Map headers) { + return Optional.ofNullable(fromValue(headers.get(HEADER_NAME))); + } + + public static WorkflowHttpContentType fromValue(Object value) { + if (value == null) { + return null; + } + String candidate = value.toString(); + int indexOf = candidate.indexOf(';'); + if (indexOf != -1) { + candidate = candidate.substring(0, indexOf); + } + candidate = candidate.trim(); for (WorkflowHttpContentType type : WorkflowHttpContentType.values()) { - if (type.value.equalsIgnoreCase(value)) { - return Optional.of(type); + if (type.value.equalsIgnoreCase(candidate)) { + return type; } } - return Optional.empty(); + return null; } public String toString() { diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestHelper.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestHelper.java index 5e2595ae3..7160faf68 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestHelper.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestHelper.java @@ -18,6 +18,7 @@ import java.net.SocketTimeoutException; import java.net.http.HttpTimeoutException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.TimeoutException; import java.util.function.BiConsumer; import org.openworkflow.sdk.impl.TaskContext; @@ -80,20 +81,21 @@ public static CompletableFuture asWorkflowHtppResponse( } public static WorkflowException fromError(Throwable ex, TaskContext task) { - throw new WorkflowException( - WorkflowError.communication(errorCodeFromException(ex.getCause()), task, ex).build(), ex); - } - - private static int errorCodeFromException(Throwable ex) { - while (ex != null) { - if (ex instanceof TimeoutException - || ex instanceof SocketTimeoutException - || ex instanceof HttpTimeoutException) { - return Errors.TIMEOUT.status(); + int statusCode = Errors.COMMUNICATION.status(); + ex = ex instanceof CompletionException ? ex.getCause() : ex; + Throwable cause = ex; + do { + if (cause instanceof TimeoutException + || cause instanceof SocketTimeoutException + || cause instanceof HttpTimeoutException) { + statusCode = Errors.TIMEOUT.status(); + break; + } else if (cause instanceof WorkflowException wfe) { + return wfe; } - ex = ex.getCause(); - } - return Errors.COMMUNICATION.status(); + cause = cause.getCause(); + } while (cause != null); + return new WorkflowException(WorkflowError.communication(statusCode, task, ex).build(), ex); } private static void validateStatus( diff --git a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestInfo.java b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestInfo.java index 53f01e4a6..e8ac42f95 100644 --- a/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestInfo.java +++ b/impl/core/src/main/java/org/openworkflow/sdk/impl/http/WorkflowHttpRequestInfo.java @@ -19,11 +19,11 @@ import java.time.Instant; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; -import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.TreeMap; import java.util.stream.Collectors; import org.openworkflow.sdk.impl.WorkflowModel; import org.openworkflow.sdk.impl.WorkflowUtils; @@ -44,8 +44,7 @@ public static Builder builder(String method) { } public WorkflowHttpContentType contentType() { - return WorkflowHttpContentType.fromValue( - (String) headers.get(WorkflowHttpContentType.HEADER_NAME)) + return WorkflowHttpContentType.fromHeaders(headers) .orElse(WorkflowHttpContentType.APPLICATION_JSON); } @@ -73,15 +72,17 @@ public Builder redirect(boolean redirect) { } public Builder headers(Map headers) { - this.headers = headers; - return this; - } - - public Builder header(String name, Object value) { - if (headers == null) { - headers = new HashMap(); + if (headers != null) { + this.headers = + headers.entrySet().stream() + .filter(e -> e.getValue() != null) + .collect( + Collectors.toMap( + Entry::getKey, + Entry::getValue, + (oldEntry, newEntry) -> newEntry, + () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER))); } - headers.put(name, value); return this; } @@ -91,7 +92,12 @@ public Builder path(URI path) { } public Builder queryParams(Map queryParams) { - this.queryParams = queryParams; + if (queryParams != null) { + this.queryParams = + queryParams.entrySet().stream() + .collect( + Collectors.toMap(Entry::getKey, e -> e.getValue() == null ? "" : e.getValue())); + } return this; } @@ -115,17 +121,8 @@ public WorkflowHttpRequestInfo build(URI uri) { path == null ? uri : WorkflowUtils.concatURI(uri, path), method, redirect, - headers != null - ? headers.entrySet().stream() - .filter(e -> e.getValue() != null) - .collect(Collectors.toMap(Entry::getKey, Entry::getValue)) - : Map.of(), - queryParams != null - ? queryParams.entrySet().stream() - .collect( - Collectors.toMap( - Entry::getKey, e -> e.getValue() == null ? "" : e.getValue())) - : Map.of(), + headers != null ? headers : Map.of(), + queryParams != null ? queryParams : Map.of(), body, auth, ifModifiedSince != null diff --git a/impl/core/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.executors.CallableTaskBuilder b/impl/core/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.executors.CallableTaskBuilder index 27dfcf972..a579139c1 100644 --- a/impl/core/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.executors.CallableTaskBuilder +++ b/impl/core/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.executors.CallableTaskBuilder @@ -1,2 +1,3 @@ org.openworkflow.sdk.impl.executors.CallFunctionExecutorBuilder -org.openworkflow.sdk.impl.executors.http.CallableTaskHttpExecutorBuilder \ No newline at end of file +org.openworkflow.sdk.impl.executors.http.CallableTaskHttpExecutorBuilder +org.openworkflow.sdk.impl.executors.openapi.OpenAPIExecutorBuilder \ No newline at end of file diff --git a/impl/openapi/src/test/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIProcessorTest.java b/impl/core/src/test/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIProcessorTest.java similarity index 100% rename from impl/openapi/src/test/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIProcessorTest.java rename to impl/core/src/test/java/org/openworkflow/sdk/impl/executors/openapi/OpenAPIProcessorTest.java diff --git a/impl/core/src/test/java/org/openworkflow/sdk/impl/http/DefaultURITemplateResolverTest.java b/impl/core/src/test/java/org/openworkflow/sdk/impl/http/DefaultURITemplateResolverTest.java new file mode 100644 index 000000000..ec90d115f --- /dev/null +++ b/impl/core/src/test/java/org/openworkflow/sdk/impl/http/DefaultURITemplateResolverTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2020-Present The Open Workflow Specification Authors + * + * 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 org.openworkflow.sdk.impl.http; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URI; +import java.util.Map; +import org.junit.jupiter.api.Test; + +public class DefaultURITemplateResolverTest { + + @Test + public void testResolve() { + URITemplateResolver resolver = new DefaultURITemplateResolver(); + assertThat( + resolver.resolveTemplates( + "https://api.example.com/users/{userId}/posts/{post-Id}", + Map.of("userId", "123", "post-Id", "456"))) + .isEqualTo(URI.create("https://api.example.com/users/123/posts/456")); + } + + @Test + public void testResolveOptional() { + URITemplateResolver resolver = new DefaultURITemplateResolver(); + assertThat( + resolver.resolveTemplates( + "https://api.example.com/pets{?limit,offset}", Map.of("limit", "10"))) + .isEqualTo(URI.create("https://api.example.com/pets?limit=10")); + } +} diff --git a/impl/openapi/src/test/resources/schema/openapi/petstore.json b/impl/core/src/test/resources/schema/openapi/petstore.json similarity index 100% rename from impl/openapi/src/test/resources/schema/openapi/petstore.json rename to impl/core/src/test/resources/schema/openapi/petstore.json diff --git a/impl/openapi/src/test/resources/schema/swagger/petstore.json b/impl/core/src/test/resources/schema/swagger/petstore.json similarity index 100% rename from impl/openapi/src/test/resources/schema/swagger/petstore.json rename to impl/core/src/test/resources/schema/swagger/petstore.json diff --git a/impl/jwt-impl/pom.xml b/impl/http-jackson/pom.xml similarity index 90% rename from impl/jwt-impl/pom.xml rename to impl/http-jackson/pom.xml index d7d31c370..50d0ef55b 100644 --- a/impl/jwt-impl/pom.xml +++ b/impl/http-jackson/pom.xml @@ -7,8 +7,8 @@ 8.0.0-SNAPSHOT - openworkflow-impl-jackson-jwt - Open Workflow :: Impl :: JWT :: Jackson + openworkflow-impl-http-jackson + Open Workflow :: Impl :: Http :: Jackson diff --git a/impl/jwt-impl/src/main/java/org/openworkflow/sdk/impl/executors/http/jackson/JacksonModelConverterFactory.java b/impl/http-jackson/src/main/java/org/openworkflow/sdk/impl/executors/http/jackson/JacksonModelConverterFactory.java similarity index 100% rename from impl/jwt-impl/src/main/java/org/openworkflow/sdk/impl/executors/http/jackson/JacksonModelConverterFactory.java rename to impl/http-jackson/src/main/java/org/openworkflow/sdk/impl/executors/http/jackson/JacksonModelConverterFactory.java diff --git a/impl/jwt-impl/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.additional.NamedSuppliedAdditionalObject b/impl/http-jackson/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.additional.NamedSuppliedAdditionalObject similarity index 100% rename from impl/jwt-impl/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.additional.NamedSuppliedAdditionalObject rename to impl/http-jackson/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.additional.NamedSuppliedAdditionalObject diff --git a/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/http/JakartaRequestExecutor.java b/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/http/JakartaRequestExecutor.java index c55a507de..7de745728 100644 --- a/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/http/JakartaRequestExecutor.java +++ b/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/http/JakartaRequestExecutor.java @@ -89,7 +89,18 @@ public CompletableFuture invoke( doRequest( request, HttpConverterResolver.converter(application, workflowContext, taskContext), - taskContext)); + taskContext)) + .exceptionallyCompose( + ex -> + ex instanceof WebApplicationException webAppEx + ? CompletableFuture.failedFuture( + new WorkflowException( + WorkflowError.communication( + webAppEx.getResponse().getStatus(), taskContext, webAppEx) + .build(), + webAppEx)) + : CompletableFuture.failedFuture( + WorkflowHttpRequestHelper.fromError(ex, taskContext))); } private Entity buildBody(HttpModelConverter converter) { @@ -108,7 +119,7 @@ private Entity buildForm() { () -> new IllegalStateException( "Workflow model should support asMap in order form_url_enconded to work")) - .forEach((k, v) -> form.param(k, v.toString())); + .forEach((k, v) -> form.param(k, v == null ? "" : v.toString())); return Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED); } @@ -119,16 +130,6 @@ private CompletableFuture doRequest( ? request.rx().method(requestInfo.method()) : request.rx().method(requestInfo.method(), buildBody(converter)); return future - .exceptionallyCompose( - ex -> - ex instanceof WebApplicationException webAppEx - ? CompletableFuture.failedFuture( - new WorkflowException( - WorkflowError.communication( - webAppEx.getResponse().getStatus(), task, webAppEx) - .build(), - webAppEx)) - : CompletableFuture.failedFuture(WorkflowHttpRequestHelper.fromError(ex, task))) .toCompletableFuture() .thenApply(r -> new JakartaHttpResponse(application, task, converter, r)); } diff --git a/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/template/JaxrsURITemplateResolver.java b/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/template/JaxrsURITemplateResolver.java index 9db4ab7fd..707aae58a 100644 --- a/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/template/JaxrsURITemplateResolver.java +++ b/impl/http/src/main/java/org/openworkflow/sdk/impl/jakarta/template/JaxrsURITemplateResolver.java @@ -17,18 +17,12 @@ import jakarta.ws.rs.core.UriBuilder; import java.net.URI; -import java.util.Collections; -import org.openworkflow.sdk.impl.TaskContext; -import org.openworkflow.sdk.impl.WorkflowContext; -import org.openworkflow.sdk.impl.WorkflowModel; +import java.util.Map; import org.openworkflow.sdk.impl.http.URITemplateResolver; public class JaxrsURITemplateResolver implements URITemplateResolver { @Override - public URI resolveTemplates( - String uri, WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel model) { - return UriBuilder.fromPath(uri) - .resolveTemplates(model.asMap().orElse(Collections.emptyMap())) - .build(); + public URI resolveTemplates(String uri, Map map) { + return UriBuilder.fromPath(uri).resolveTemplates(map).build(); } } diff --git a/impl/jackson/pom.xml b/impl/jackson/pom.xml index dcd550d74..0436c8226 100644 --- a/impl/jackson/pom.xml +++ b/impl/jackson/pom.xml @@ -36,5 +36,9 @@ org.openworkflow.sdk openworkflow-impl-cron + + org.openworkflow.sdk + openworkflow-impl-openapi + \ No newline at end of file diff --git a/impl/jwt-impl/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.auth.JWTConverter b/impl/jwt-impl/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.auth.JWTConverter deleted file mode 100644 index 8f03b08b6..000000000 --- a/impl/jwt-impl/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.auth.JWTConverter +++ /dev/null @@ -1 +0,0 @@ -org.openworkflow.sdk.impl.executors.http.oauth.jackson.JacksonJWTConverter \ No newline at end of file diff --git a/impl/jwt-impl/src/main/java/org/openworkflow/sdk/impl/executors/http/oauth/jackson/JacksonJWTConverter.java b/impl/model/src/main/java/org/openworkflow/sdk/impl/oauth/jackson/JacksonJWTConverter.java similarity index 97% rename from impl/jwt-impl/src/main/java/org/openworkflow/sdk/impl/executors/http/oauth/jackson/JacksonJWTConverter.java rename to impl/model/src/main/java/org/openworkflow/sdk/impl/oauth/jackson/JacksonJWTConverter.java index d7611bb23..6cf5b9757 100644 --- a/impl/jwt-impl/src/main/java/org/openworkflow/sdk/impl/executors/http/oauth/jackson/JacksonJWTConverter.java +++ b/impl/model/src/main/java/org/openworkflow/sdk/impl/oauth/jackson/JacksonJWTConverter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openworkflow.sdk.impl.executors.http.oauth.jackson; +package org.openworkflow.sdk.impl.oauth.jackson; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -28,7 +28,6 @@ public class JacksonJWTConverter implements JWTConverter { @Override public JWT fromToken(byte[] bytes) throws IllegalArgumentException { - Map map = JsonUtils.mapper().readValue(bytes, new TypeReference>() {}); String token = (String) map.get("access_token"); diff --git a/impl/model/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.auth.JWTConverter b/impl/model/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.auth.JWTConverter new file mode 100644 index 000000000..9763ab79d --- /dev/null +++ b/impl/model/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.auth.JWTConverter @@ -0,0 +1 @@ +org.openworkflow.sdk.impl.oauth.jackson.JacksonJWTConverter \ No newline at end of file diff --git a/impl/jwt-impl/src/test/java/org/openworkflow/sdk/impl/http/jwt/JacksonJWTImplTest.java b/impl/model/src/test/java/org/openworkflow/sdk/impl/auth/jwt/jackson/JacksonJWTImplTest.java similarity index 74% rename from impl/jwt-impl/src/test/java/org/openworkflow/sdk/impl/http/jwt/JacksonJWTImplTest.java rename to impl/model/src/test/java/org/openworkflow/sdk/impl/auth/jwt/jackson/JacksonJWTImplTest.java index 131209fc5..8b4872a21 100644 --- a/impl/jwt-impl/src/test/java/org/openworkflow/sdk/impl/http/jwt/JacksonJWTImplTest.java +++ b/impl/model/src/test/java/org/openworkflow/sdk/impl/auth/jwt/jackson/JacksonJWTImplTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openworkflow.sdk.impl.http.jwt; +package org.openworkflow.sdk.impl.auth.jwt.jackson; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -26,15 +26,18 @@ import java.util.Optional; import org.junit.jupiter.api.Test; import org.openworkflow.sdk.impl.auth.JWT; -import org.openworkflow.sdk.impl.executors.http.oauth.jackson.JacksonJWTConverter; import org.openworkflow.sdk.impl.jackson.JsonUtils; +import org.openworkflow.sdk.impl.oauth.jackson.JacksonJWTConverter; public class JacksonJWTImplTest { private static final String BEARER_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJTY1c4RGI5RThtSnlvTUNqZHVtek5VR21FcG9MYURDb19ralZkVHJ2NDdVIn0.eyJleHAiOjE3NTYxNDgyNTcsImlhdCI6MTc1NjE0Nzk1NywianRpIjoiOWU4YzZjMWItZDBmZS00NGNhLThlOTgtNzNkZTY4OTdjYzE4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDg4L3JlYWxtcy90ZXN0LXJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImExZDdlMWVlLTVmZjMtNDc3Yi1hMmRhLTNhNDZkYThlZmRkMSIsInR5cCI6IkJlYXJlciIsImF6cCI6InNlcnZlcmxlc3Mtd29ya2Zsb3ciLCJzZXNzaW9uX3N0YXRlIjoiNDdiYzIxZDEtYTMyYi00OGJlLWI2OWQtZDM5MjE5MjViZTlmIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtdGVzdC1yZWFsbSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6InByb2ZpbGUgZW1haWwiLCJzaWQiOiI0N2JjMjFkMS1hMzJiLTQ4YmUtYjY5ZC1kMzkyMTkyNWJlOWYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJEbWl0cmlpIFRpa2hvbWlyb3YiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2ZXJsZXNzLXdvcmtmbG93LXRlc3QiLCJnaXZlbl9uYW1lIjoiRG1pdHJpaSIsImZhbWlseV9uYW1lIjoiVGlraG9taXJvdiIsImVtYWlsIjoiYW55QGV4YW1wbGUub3JnIn0.bY9DqVt5jPcfsY5-tL4JbXXptnyFBBLLKuViBH3xQBXpHSmtXKK78BjBdUOcCAg6SMatdQ4XK13RJJNPpjPDCkuBeLDMwsdCrzKcfaUWY7JCMfam4gIiC989m_S8y8jtJovDst8sjIkn95f5izuuiAxRYw69_IcY__cfBw7k0OhL7Y_YXCcwwz7l2yrbplmpLiakTTuzhbiCER5EPohguy9BkYG_u2RDUZg0Rvy3EzbyVhTQQyfKRFjoAxTa1Se484n2lXqgMn1JHTZLwrgXAjcMDVaktktLb_cn276ygAeuqPj7dOsQGEoLR-8enRz1eZKBPgO70LNqGkkkyHiyOA"; - private static final byte[] JWT_TOKEN = - JsonUtils.mapper().writeValueAsBytes(Map.of("access_token", BEARER_TOKEN)); + private static final byte[] JWT_TOKEN = convert(BEARER_TOKEN); + + private static byte[] convert(String token) { + return JsonUtils.mapper().writeValueAsBytes(Map.of("access_token", token)); + } private static final JacksonJWTConverter converter = new JacksonJWTConverter(); @@ -86,6 +89,40 @@ void typedClaimAccess() { @Test void nullToken() { - assertThrows(IllegalArgumentException.class, () -> converter.fromToken(null)); + assertThrows( + IllegalArgumentException.class, + () -> converter.fromToken(JsonUtils.mapper().writeValueAsBytes(Map.of()))); + } + + @Test + void blankToken() { + assertThrows(IllegalArgumentException.class, () -> converter.fromToken(convert(" "))); + } + + @Test + void wrongPartsCount() { + assertThrows(IllegalArgumentException.class, () -> converter.fromToken(convert("a.b"))); + assertThrows(IllegalArgumentException.class, () -> converter.fromToken(convert("a.b.c.d"))); + } + + @Test + void badBase64Header() { + String bad = "###." + BEARER_TOKEN.split("\\.")[1] + "." + BEARER_TOKEN.split("\\.")[2]; + assertThrows(IllegalArgumentException.class, () -> converter.fromToken(convert(bad))); + } + + @Test + void badBase64Payload() { + String[] p = BEARER_TOKEN.split("\\."); + String bad = p[0] + ".###." + p[2]; + assertThrows(IllegalArgumentException.class, () -> converter.fromToken(convert(bad))); + } + + @Test + void badJson() { + String[] p = BEARER_TOKEN.split("\\."); + String notJsonB64url = "bm90LWpzb24"; + String bad = p[0] + "." + notJsonB64url + "." + p[2]; + assertThrows(IllegalArgumentException.class, () -> converter.fromToken(convert(bad))); } } diff --git a/impl/openapi-jackson/pom.xml b/impl/openapi-jackson/pom.xml index 3b88edf43..aca3c00cf 100644 --- a/impl/openapi-jackson/pom.xml +++ b/impl/openapi-jackson/pom.xml @@ -10,7 +10,7 @@ org.openworkflow.sdk - openworkflow-impl-openapi-base + openworkflow-impl-core org.openworkflow.sdk @@ -18,7 +18,7 @@ org.openworkflow.sdk - openworkflow-impl-openapi-base + openworkflow-impl-core test-jar test diff --git a/impl/openapi/pom.xml b/impl/openapi/pom.xml deleted file mode 100644 index 0d1e3eba3..000000000 --- a/impl/openapi/pom.xml +++ /dev/null @@ -1,50 +0,0 @@ - - 4.0.0 - - org.openworkflow.sdk - openworkflow-impl - 8.0.0-SNAPSHOT - - openworkflow-impl-openapi-base - Open Workflow :: Impl :: OpenAPI :: Base - - - jakarta.ws.rs - jakarta.ws.rs-api - - - org.openworkflow.sdk - openworkflow-impl-core - - - org.openworkflow.sdk - openworkflow-impl-http - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.junit.jupiter - junit-jupiter-params - test - - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - test-jar - - - - - - - diff --git a/impl/openapi/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.executors.CallableTaskBuilder b/impl/openapi/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.executors.CallableTaskBuilder deleted file mode 100644 index 890d62786..000000000 --- a/impl/openapi/src/main/resources/META-INF/services/org.openworkflow.sdk.impl.executors.CallableTaskBuilder +++ /dev/null @@ -1 +0,0 @@ -org.openworkflow.sdk.impl.executors.openapi.OpenAPIExecutorBuilder diff --git a/impl/pom.xml b/impl/pom.xml index 2352763cf..38d90cc51 100644 --- a/impl/pom.xml +++ b/impl/pom.xml @@ -85,7 +85,7 @@ org.openworkflow.sdk - openworkflow-impl-jackson-jwt + openworkflow-impl-http-jackson ${project.version} @@ -95,12 +95,7 @@ org.openworkflow.sdk - openworkflow-impl-openapi-base - ${project.version} - - - org.openworkflow.sdk - openworkflow-impl-openapi-base + openworkflow-impl-core ${project.version} test-jar test @@ -213,10 +208,9 @@ core http + http-jackson jackson - jwt-impl persistence - openapi jq json-utils model diff --git a/impl/test/pom.xml b/impl/test/pom.xml index 542b2c3ef..11e050c2a 100644 --- a/impl/test/pom.xml +++ b/impl/test/pom.xml @@ -32,7 +32,7 @@ org.openworkflow.sdk - openworkflow-impl-jackson-jwt + openworkflow-impl-http-jackson org.openworkflow.sdk diff --git a/impl/test/src/test/java/org/openworkflow/sdk/impl/auth/OAuthRequestBuilderTest.java b/impl/test/src/test/java/org/openworkflow/sdk/impl/auth/OAuthRequestBuilderTest.java index c5553f69c..36fa515c8 100644 --- a/impl/test/src/test/java/org/openworkflow/sdk/impl/auth/OAuthRequestBuilderTest.java +++ b/impl/test/src/test/java/org/openworkflow/sdk/impl/auth/OAuthRequestBuilderTest.java @@ -60,6 +60,7 @@ void resolvesConfiguredEndpointsAndNormalizesLeadingSlash() { @Test void appliesDefaultTokenPathAndLeavesManagementEndpointsUnconfigured() { AuthHttpRequestInfo info = build(baseProperties()); + assertEquals( URI.create("http://localhost:8888/realms/test-realm/oauth2/token"), info.uri().apply(null, null, null)); diff --git a/impl/test/src/test/java/org/openworkflow/sdk/impl/test/ActiveInstanceRegistrationTest.java b/impl/test/src/test/java/org/openworkflow/sdk/impl/test/ActiveInstanceRegistrationTest.java index d3092215f..2afa3d038 100644 --- a/impl/test/src/test/java/org/openworkflow/sdk/impl/test/ActiveInstanceRegistrationTest.java +++ b/impl/test/src/test/java/org/openworkflow/sdk/impl/test/ActiveInstanceRegistrationTest.java @@ -85,7 +85,6 @@ void startingSecondInstanceWithSameIdFails() throws IOException { .withCauseInstanceOf(IllegalStateException.class); assertThat(listener.events).isEmpty(); assertThat(second.status()).isEqualTo(WorkflowStatus.PENDING); - assertThat(definition.activeInstance("same-id")).containsSame(first); } }