diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/AbstractAuthRequestBuilder.java b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/AbstractAuthRequestBuilder.java index b145d9906..15ba3046b 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/AbstractAuthRequestBuilder.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/AbstractAuthRequestBuilder.java @@ -17,15 +17,24 @@ import static io.serverlessworkflow.api.types.OAuth2AuthenticationDataClient.ClientAuthentication.CLIENT_SECRET_POST; import static io.serverlessworkflow.impl.WorkflowUtils.isValid; +import static io.serverlessworkflow.impl.auth.AuthUtils.ACTOR; +import static io.serverlessworkflow.impl.auth.AuthUtils.ACTOR_TOKEN; +import static io.serverlessworkflow.impl.auth.AuthUtils.ACTOR_TOKEN_TYPE; import static io.serverlessworkflow.impl.auth.AuthUtils.AUDIENCES; import static io.serverlessworkflow.impl.auth.AuthUtils.AUTHENTICATION; import static io.serverlessworkflow.impl.auth.AuthUtils.CLIENT; import static io.serverlessworkflow.impl.auth.AuthUtils.ENCODING; import static io.serverlessworkflow.impl.auth.AuthUtils.REQUEST; import static io.serverlessworkflow.impl.auth.AuthUtils.SCOPES; +import static io.serverlessworkflow.impl.auth.AuthUtils.SUBJECT; +import static io.serverlessworkflow.impl.auth.AuthUtils.SUBJECT_TOKEN; +import static io.serverlessworkflow.impl.auth.AuthUtils.SUBJECT_TOKEN_TYPE; +import static io.serverlessworkflow.impl.auth.AuthUtils.TOKEN; +import static io.serverlessworkflow.impl.auth.AuthUtils.TYPE; import io.serverlessworkflow.api.types.OAuth2AuthenticationData; import io.serverlessworkflow.api.types.OAuth2AuthenticationDataClient; +import io.serverlessworkflow.api.types.OAuth2TokenDefinition; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowUtils; import java.util.Arrays; @@ -51,6 +60,7 @@ public HttpRequestInfo apply(T authenticationData) { audience(authenticationData); scope(authenticationData); authenticationMethod(authenticationData); + subjectActor(authenticationData); return requestBuilder.build(); } @@ -61,6 +71,7 @@ public HttpRequestInfo apply(Map secret) { audience(secret); scope(secret); authenticationMethod(secret); + subjectActor(secret); return requestBuilder.build(); } @@ -80,20 +91,17 @@ protected void audience(Map secret) { } protected void authenticationMethod(T authenticationData) { - ClientSecretHandler secretHandler; - switch (getClientAuthentication(authenticationData)) { - case CLIENT_SECRET_BASIC: - secretHandler = new ClientSecretBasic(application, requestBuilder); - case CLIENT_SECRET_JWT: - throw new UnsupportedOperationException("Client Secret JWT is not supported yet"); - case PRIVATE_KEY_JWT: - throw new UnsupportedOperationException("Private Key JWT is not supported yet"); - default: - secretHandler = new ClientSecretPost(application, requestBuilder); - } + ClientSecretHandler secretHandler = + switch (getClientAuthentication(authenticationData)) { + case CLIENT_SECRET_BASIC -> new ClientSecretBasic(application, requestBuilder); + case CLIENT_SECRET_JWT, PRIVATE_KEY_JWT -> + new JwtClientAssertion(application, requestBuilder); + default -> new ClientSecretPost(application, requestBuilder); + }; secretHandler.accept(authenticationData); } + @SuppressWarnings("unchecked") protected void authenticationMethod(Map secret) { Map client = (Map) secret.get(CLIENT); ClientSecretHandler secretHandler; @@ -101,23 +109,48 @@ protected void authenticationMethod(Map secret) { if (auth == null) { secretHandler = new ClientSecretPost(application, requestBuilder); } else { - switch (auth) { - case "client_secret_basic": - secretHandler = new ClientSecretBasic(application, requestBuilder); - break; - default: - case "client_secret_post": - secretHandler = new ClientSecretPost(application, requestBuilder); - break; - case "private_key_jwt": - throw new UnsupportedOperationException("Private Key JWT is not supported yet"); - case "client_secret_jwt": - throw new UnsupportedOperationException("Client Secret JWT is not supported yet"); - } + secretHandler = + switch (auth) { + case "client_secret_basic" -> new ClientSecretBasic(application, requestBuilder); + case "private_key_jwt", "client_secret_jwt" -> + new JwtClientAssertion(application, requestBuilder); + default -> new ClientSecretPost(application, requestBuilder); + }; } secretHandler.accept(secret); } + protected void subjectActor(T authenticationData) { + tokenParam(SUBJECT_TOKEN, SUBJECT_TOKEN_TYPE, authenticationData.getSubject()); + tokenParam(ACTOR_TOKEN, ACTOR_TOKEN_TYPE, authenticationData.getActor()); + } + + private void tokenParam(String tokenKey, String typeKey, OAuth2TokenDefinition definition) { + if (definition != null) { + requestBuilder + .addQueryParam( + tokenKey, WorkflowUtils.buildStringFilter(application, definition.getToken())) + .addQueryParam( + typeKey, WorkflowUtils.buildStringFilter(application, definition.getType())); + } + } + + protected void subjectActor(Map secret) { + tokenParam(SUBJECT_TOKEN, SUBJECT_TOKEN_TYPE, secret.get(SUBJECT)); + tokenParam(ACTOR_TOKEN, ACTOR_TOKEN_TYPE, secret.get(ACTOR)); + } + + private void tokenParam(String tokenKey, String typeKey, Object rawDefinition) { + if (rawDefinition instanceof Map definition) { + requestBuilder + .addQueryParam( + tokenKey, + WorkflowUtils.buildStringFilter(application, (String) definition.get(TOKEN))) + .addQueryParam( + typeKey, WorkflowUtils.buildStringFilter(application, (String) definition.get(TYPE))); + } + } + private OAuth2AuthenticationDataClient.ClientAuthentication getClientAuthentication( OAuth2AuthenticationData authenticationData) { return authenticationData.getClient() == null diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/AuthUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/AuthUtils.java index 7dc388827..28daba2a4 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/AuthUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/AuthUtils.java @@ -35,6 +35,23 @@ private AuthUtils() {} public static final String REQUEST = "request"; public static final String ENCODING = "encoding"; public static final String AUTHENTICATION = "authentication"; + public static final String ASSERTION = "assertion"; + public static final String SUBJECT = "subject"; + public static final String ACTOR = "actor"; + public static final String TYPE = "type"; + public static final String REVOCATION = "revocation"; + public static final String INTROSPECTION = "introspection"; + + public static final String CLIENT_ID = "client_id"; + public static final String CLIENT_ASSERTION = "client_assertion"; + public static final String CLIENT_ASSERTION_TYPE = "client_assertion_type"; + public static final String JWT_BEARER_ASSERTION_TYPE = + "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"; + + public static final String SUBJECT_TOKEN = "subject_token"; + public static final String SUBJECT_TOKEN_TYPE = "subject_token_type"; + public static final String ACTOR_TOKEN = "actor_token"; + public static final String ACTOR_TOKEN_TYPE = "actor_token_type"; private static final String AUTH_HEADER_FORMAT = "%s %s"; diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/ClientSecretHandler.java b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/ClientSecretHandler.java index b4be0f885..491f32ec9 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/ClientSecretHandler.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/ClientSecretHandler.java @@ -17,6 +17,7 @@ import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS; import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.PASSWORD; +import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.URN_IETF_PARAMS_OAUTH_GRANT_TYPE_TOKEN_EXCHANGE; import io.serverlessworkflow.api.types.OAuth2AuthenticationData; import io.serverlessworkflow.impl.WorkflowApplication; @@ -48,7 +49,8 @@ void accept(OAuth2AuthenticationData authenticationData) { } password(authenticationData); - } else if (authenticationData.getGrant().equals(CLIENT_CREDENTIALS)) { + } else if (authenticationData.getGrant().equals(CLIENT_CREDENTIALS) + || authenticationData.getGrant().equals(URN_IETF_PARAMS_OAUTH_GRANT_TYPE_TOKEN_EXCHANGE)) { if (authenticationData.getClient() == null || authenticationData.getClient().getId() == null || authenticationData.getClient().getSecret() == null) { @@ -74,6 +76,7 @@ void accept(Map secret) { String grant = Objects.requireNonNull((String) secret.get("grant"), "Grant is mandatory field"); switch (grant) { case "client_credentials": + case "urn:ietf:params:oauth:grant-type:token-exchange": clientCredentials(secret); break; case "password": diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/ClientSecretPost.java b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/ClientSecretPost.java index 6440f869f..ad07343a3 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/ClientSecretPost.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/ClientSecretPost.java @@ -38,10 +38,10 @@ protected ClientSecretPost( protected void clientCredentials(OAuth2AuthenticationData authenticationData) { requestBuilder .withGrantType(authenticationData.getGrant().value()) - .addQueryParam( + .addClientAuthParam( "client_id", WorkflowUtils.buildStringFilter(application, authenticationData.getClient().getId())) - .addQueryParam( + .addClientAuthParam( "client_secret", WorkflowUtils.buildStringFilter( application, authenticationData.getClient().getSecret())); @@ -64,8 +64,8 @@ protected void clientCredentials(Map secret) { Map client = (Map) secret.get(CLIENT); requestBuilder .withGrantType((String) secret.get(GRANT)) - .addQueryParam("client_id", (String) client.get(ID)) - .addQueryParam("client_secret", (String) client.get(SECRET)); + .addClientAuthParam("client_id", (String) client.get(ID)) + .addClientAuthParam("client_secret", (String) client.get(SECRET)); } @Override diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/HttpRequestInfo.java b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/HttpRequestInfo.java index c7a754079..d3850e8e6 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/HttpRequestInfo.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/HttpRequestInfo.java @@ -18,10 +18,14 @@ import io.serverlessworkflow.impl.WorkflowValueResolver; import java.net.URI; import java.util.Map; +import java.util.Optional; public record HttpRequestInfo( Map> headers, Map> queryParams, + Map> clientAuthParams, WorkflowValueResolver uri, + Optional> revocationUri, + Optional> introspectionUri, String grantType, String contentType) {} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/HttpRequestInfoBuilder.java b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/HttpRequestInfoBuilder.java index c948ad3a1..fb021d222 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/HttpRequestInfoBuilder.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/HttpRequestInfoBuilder.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +import java.util.Optional; class HttpRequestInfoBuilder { @@ -30,8 +31,14 @@ class HttpRequestInfoBuilder { private Map> queryParams; + private Map> clientAuthParams; + private WorkflowValueResolver uri; + private Optional> revocationUri = Optional.empty(); + + private Optional> introspectionUri = Optional.empty(); + private String grantType; private String contentType; @@ -39,6 +46,7 @@ class HttpRequestInfoBuilder { HttpRequestInfoBuilder() { headers = new HashMap<>(); queryParams = new HashMap<>(); + clientAuthParams = new HashMap<>(); } HttpRequestInfoBuilder addHeader(String key, String token) { @@ -61,11 +69,32 @@ HttpRequestInfoBuilder addQueryParam(String key, WorkflowValueResolver t return this; } + HttpRequestInfoBuilder addClientAuthParam(String key, String token) { + clientAuthParams.put(key, (w, t, m) -> token); + return this; + } + + HttpRequestInfoBuilder addClientAuthParam(String key, WorkflowValueResolver token) { + clientAuthParams.put(key, token); + return this; + } + HttpRequestInfoBuilder withUri(WorkflowValueResolver uri) { this.uri = uri; return this; } + HttpRequestInfoBuilder withRevocationUri(Optional> revocationUri) { + this.revocationUri = revocationUri; + return this; + } + + HttpRequestInfoBuilder withIntrospectionUri( + Optional> introspectionUri) { + this.introspectionUri = introspectionUri; + return this; + } + HttpRequestInfoBuilder withContentType(OAuth2TokenRequest oAuth2TokenRequest) { if (oAuth2TokenRequest != null) { this.contentType = oAuth2TokenRequest.getEncoding().value(); @@ -91,6 +120,14 @@ HttpRequestInfo build() { if (contentType == null) { contentType = APPLICATION_X_WWW_FORM_URLENCODED.value(); } - return new HttpRequestInfo(headers, queryParams, uri, grantType, contentType); + return new HttpRequestInfo( + headers, + queryParams, + clientAuthParams, + uri, + revocationUri, + introspectionUri, + grantType, + contentType); } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/JwtClientAssertion.java b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/JwtClientAssertion.java new file mode 100644 index 000000000..3a601595d --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/JwtClientAssertion.java @@ -0,0 +1,139 @@ +/* + * Copyright 2020-Present The Serverless 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 io.serverlessworkflow.impl.auth; + +import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.PASSWORD; +import static io.serverlessworkflow.impl.auth.AuthUtils.ASSERTION; +import static io.serverlessworkflow.impl.auth.AuthUtils.CLIENT; +import static io.serverlessworkflow.impl.auth.AuthUtils.CLIENT_ASSERTION; +import static io.serverlessworkflow.impl.auth.AuthUtils.CLIENT_ASSERTION_TYPE; +import static io.serverlessworkflow.impl.auth.AuthUtils.CLIENT_ID; +import static io.serverlessworkflow.impl.auth.AuthUtils.GRANT; +import static io.serverlessworkflow.impl.auth.AuthUtils.ID; +import static io.serverlessworkflow.impl.auth.AuthUtils.JWT_BEARER_ASSERTION_TYPE; +import static io.serverlessworkflow.impl.auth.AuthUtils.USER; + +import io.serverlessworkflow.api.types.OAuth2AuthenticationData; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowUtils; +import java.util.Map; + +/** + * Handles the {@code client_secret_jwt} and {@code private_key_jwt} client authentication methods. + * + *

Per the Serverless Workflow specification, the caller supplies a pre-signed JWT through {@code + * client.assertion}. Both methods are forwarded identically: the assertion is sent as {@code + * client_assertion} together with the standard {@code client_assertion_type} defined by RFC 7523. + * The signing algorithm (HMAC for {@code client_secret_jwt}, an asymmetric key for {@code + * private_key_jwt}) is the caller's responsibility. + */ +class JwtClientAssertion extends ClientSecretHandler { + + protected JwtClientAssertion( + WorkflowApplication application, HttpRequestInfoBuilder requestBuilder) { + super(application, requestBuilder); + } + + @Override + void accept(OAuth2AuthenticationData authenticationData) { + if (authenticationData.getClient() == null + || authenticationData.getClient().getAssertion() == null) { + throw new IllegalArgumentException( + "A client assertion must be provided for JWT client authentication"); + } + if (authenticationData.getGrant().equals(PASSWORD)) { + if (authenticationData.getUsername() == null || authenticationData.getPassword() == null) { + throw new IllegalArgumentException( + "Username and password must be provided for password grant type"); + } + password(authenticationData); + } else { + clientCredentials(authenticationData); + } + } + + @Override + void accept(Map secret) { + Map client = asClient(secret); + if (client == null || client.get(ASSERTION) == null) { + throw new IllegalArgumentException( + "A client assertion must be provided for JWT client authentication"); + } + if (PASSWORD.value().equals(secret.get(GRANT))) { + if (secret.get(USER) == null || secret.get(AuthUtils.PASSWORD) == null) { + throw new IllegalArgumentException( + "Username and password must be provided for password grant type"); + } + password(secret); + } else { + clientCredentials(secret); + } + } + + @Override + protected void clientCredentials(OAuth2AuthenticationData authenticationData) { + requestBuilder.withGrantType(authenticationData.getGrant().value()); + addAssertion( + authenticationData.getClient().getId(), authenticationData.getClient().getAssertion()); + } + + @Override + protected void password(OAuth2AuthenticationData authenticationData) { + clientCredentials(authenticationData); + requestBuilder + .addQueryParam( + "username", + WorkflowUtils.buildStringFilter(application, authenticationData.getUsername())) + .addQueryParam( + "password", + WorkflowUtils.buildStringFilter(application, authenticationData.getPassword())); + } + + @Override + protected void clientCredentials(Map secret) { + Map client = asClient(secret); + requestBuilder.withGrantType((String) secret.get(GRANT)); + addAssertion((String) client.get(ID), (String) client.get(ASSERTION)); + } + + @Override + protected void password(Map secret) { + clientCredentials(secret); + requestBuilder + .addQueryParam("username", (String) secret.get(USER)) + .addQueryParam("password", (String) secret.get(AuthUtils.PASSWORD)); + } + + private void addAssertion(String clientId, String assertion) { + if (clientId != null) { + requestBuilder.addClientAuthParam( + CLIENT_ID, WorkflowUtils.buildStringFilter(application, clientId)); + } + requestBuilder + .addClientAuthParam(CLIENT_ASSERTION_TYPE, JWT_BEARER_ASSERTION_TYPE) + .addClientAuthParam( + CLIENT_ASSERTION, WorkflowUtils.buildStringFilter(application, assertion)); + } + + @SuppressWarnings("unchecked") + private static Map asClient(Map secret) { + Object client = secret.get(CLIENT); + if (client != null && !(client instanceof Map)) { + throw new IllegalArgumentException("The 'client' entry must be a map"); + } + return (Map) client; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/OAuthRequestBuilder.java b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/OAuthRequestBuilder.java index a4766e335..436a4772a 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/OAuthRequestBuilder.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/OAuthRequestBuilder.java @@ -25,40 +25,75 @@ import io.serverlessworkflow.impl.WorkflowValueResolver; import java.net.URI; import java.util.Map; +import java.util.Optional; class OAuthRequestBuilder extends AbstractAuthRequestBuilder { - private static String DEFAULT_TOKEN_PATH = "oauth2/token"; + private static final String DEFAULT_TOKEN_PATH = "oauth2/token"; public OAuthRequestBuilder(WorkflowApplication application) { super(application); } - // TODO handle revocation and introspection path - // private static String DEFAULT_REVOCATION_PATH = "oauth2/revoke"; - // private static String DEFAULT_INTROSPECTION_PATH = "oauth2/introspect"; - @Override protected void authenticationURI(OAuth2ConnectAuthenticationProperties authenticationData) { OAuth2AuthenticationPropertiesEndpoints endpoints = authenticationData.getEndpoints(); WorkflowValueResolver uri = WorkflowUtils.getURISupplier(application, authenticationData.getAuthority()); - String tokenPath = - endpoints != null && endpoints.getToken() != null - ? endpoints.getToken().replaceAll("^/", "") - : DEFAULT_TOKEN_PATH; - requestBuilder.withUri((w, t, m) -> concatURI(uri.apply(w, t, m), tokenPath)); + String token = endpoints != null ? endpoints.getToken() : null; + String revocation = endpoints != null ? endpoints.getRevocation() : null; + String introspection = endpoints != null ? endpoints.getIntrospection() : null; + requestBuilder + .withUri(endpointResolver(uri, endpointPath(token, DEFAULT_TOKEN_PATH))) + .withRevocationUri(optionalEndpoint(uri, revocation)) + .withIntrospectionUri(optionalEndpoint(uri, introspection)); } @Override protected void authenticationURI(Map secret) { - String tokenPath = - secret.get("endpoints") instanceof Map endpoints ? (String) endpoints.get("token") : null; - URI uri = - concatURI( - URI.create((String) secret.get(AUTHORITY)), - tokenPath == null ? DEFAULT_TOKEN_PATH : tokenPath); - requestBuilder.withUri((w, t, m) -> uri); + URI authority = URI.create((String) secret.get(AUTHORITY)); + Map endpoints = + secret.get("endpoints") instanceof Map raw ? (Map) raw : Map.of(); + requestBuilder + .withUri( + staticUri(authority, endpointPath((String) endpoints.get("token"), DEFAULT_TOKEN_PATH))) + .withRevocationUri(optionalStaticUri(authority, endpoints.get("revocation"))) + .withIntrospectionUri(optionalStaticUri(authority, endpoints.get("introspection"))); + } + + private static String stripLeadingSlash(String path) { + return path.startsWith("/") ? path.substring(1) : path; + } + + private static String endpointPath(String path, String defaultPath) { + return path != null ? stripLeadingSlash(path) : defaultPath; + } + + private WorkflowValueResolver endpointResolver( + WorkflowValueResolver authority, String path) { + return (w, t, m) -> concatURI(authority.apply(w, t, m), path); + } + + // Revocation and introspection are optional capabilities: they are only wired up when the + // workflow (or secret) explicitly declares the corresponding endpoint, so that providers without + // these endpoints fail with a clear "not configured" error instead of calling a guessed path. + private Optional> optionalEndpoint( + WorkflowValueResolver authority, String path) { + return path == null + ? Optional.empty() + : Optional.of(endpointResolver(authority, stripLeadingSlash(path))); + } + + private static WorkflowValueResolver staticUri(URI authority, String path) { + URI uri = concatURI(authority, path); + return (w, t, m) -> uri; + } + + private static Optional> optionalStaticUri( + URI authority, Object path) { + return path instanceof String value + ? Optional.of(staticUri(authority, stripLeadingSlash(value))) + : Optional.empty(); } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/auth/TokenIntrospection.java b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/TokenIntrospection.java new file mode 100644 index 000000000..3f1e9be3e --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/auth/TokenIntrospection.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020-Present The Serverless 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 io.serverlessworkflow.impl.auth; + +import java.util.Map; + +/** + * The result of an OAuth2 token introspection request as defined by RFC 7662. + * + *

{@code active} is the only field guaranteed by the specification; the full response is exposed + * through {@code claims} so callers can inspect additional metadata (scope, exp, sub, ...). + */ +public record TokenIntrospection(boolean active, Map claims) {} diff --git a/impl/http/pom.xml b/impl/http/pom.xml index 484d67aac..7fa11af78 100644 --- a/impl/http/pom.xml +++ b/impl/http/pom.xml @@ -12,5 +12,35 @@ io.serverlessworkflow serverlessworkflow-impl-template-resolver + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + com.squareup.okhttp3 + mockwebserver + test + + + org.glassfish.jersey.core + jersey-client + test + + + org.glassfish.jersey.media + jersey-media-json-jackson + test + \ No newline at end of file diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/JaxRSAccessTokenProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/JaxRSAccessTokenProvider.java index 9e9a652e3..cd1f03ab6 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/JaxRSAccessTokenProvider.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/JaxRSAccessTokenProvider.java @@ -22,6 +22,7 @@ import io.serverlessworkflow.impl.WorkflowError; import io.serverlessworkflow.impl.WorkflowException; import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowValueResolver; import io.serverlessworkflow.impl.auth.AccessTokenProvider; import io.serverlessworkflow.impl.auth.HttpRequestInfo; import io.serverlessworkflow.impl.auth.JWT; @@ -37,9 +38,12 @@ import jakarta.ws.rs.core.GenericType; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; class JaxRSAccessTokenProvider implements AccessTokenProvider { @@ -71,36 +75,14 @@ public JWT validateAndGet(WorkflowContext workflow, TaskContext context, Workflo private Map invoke( WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel model) { - try { - Response response = executeRequest(workflowContext, taskContext, model); - - if (response.getStatus() < 200 || response.getStatus() >= 300) { - throw new WorkflowException( - WorkflowError.communication( - response.getStatus(), - taskContext, - "Failed to obtain token: HTTP " - + response.getStatus() - + " — " - + response.getEntity()) - .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( - -1, taskContext, "Failed to connect or process request: " + e.getMessage()) - .build(), - e); - } + return execute( + taskContext, + () -> { + try (Response response = executeRequest(workflowContext, taskContext, model)) { + ensureSuccessful(response, taskContext, "obtain token"); + return response.readEntity(new GenericType<>() {}); + } + }); } private Response executeRequest(WorkflowContext workflow, TaskContext task, WorkflowModel model) { @@ -108,19 +90,8 @@ private Response executeRequest(WorkflowContext workflow, TaskContext task, Work Client client = HttpClientResolver.client(workflow, task); WebTarget target = client.target(requestInfo.uri().apply(workflow, task, model)); - Invocation.Builder builder = target.request(MediaType.APPLICATION_JSON); - + Invocation.Builder builder = commonHeaders(target, workflow, task, model); builder.header("grant_type", requestInfo.grantType()); - 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, model); - if (headerValue != null) { - builder.header(entry.getKey(), headerValue); - } - } Entity entity; if (requestInfo.contentType().equals(APPLICATION_X_WWW_FORM_URLENCODED.value())) { @@ -129,6 +100,9 @@ private Response executeRequest(WorkflowContext workflow, TaskContext task, Work requestInfo .queryParams() .forEach((key, value) -> form.param(key, value.apply(workflow, task, model))); + requestInfo + .clientAuthParams() + .forEach((key, value) -> form.param(key, value.apply(workflow, task, model))); entity = Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED); } else { Map jsonData = new HashMap<>(); @@ -136,9 +110,105 @@ private Response executeRequest(WorkflowContext workflow, TaskContext task, Work requestInfo .queryParams() .forEach((key, value) -> jsonData.put(key, value.apply(workflow, task, model))); + requestInfo + .clientAuthParams() + .forEach((key, value) -> jsonData.put(key, value.apply(workflow, task, model))); entity = Entity.entity(jsonData, MediaType.APPLICATION_JSON); } return builder.post(entity); } + + /** + * Builds and posts a token management request (revocation per RFC 7009, introspection per RFC + * 7662). The body carries the {@code token}, an optional {@code token_type_hint} and the client + * authentication parameters; client authentication carried through headers (e.g. HTTP Basic) is + * applied as well. + */ + private Response postManagementRequest( + WorkflowContext workflow, + TaskContext task, + WorkflowModel model, + URI uri, + String token, + String tokenTypeHint) { + Invocation.Builder builder = + commonHeaders(HttpClientResolver.client(workflow, task).target(uri), workflow, task, model); + + Form form = new Form(); + form.param("token", token); + if (tokenTypeHint != null) { + form.param("token_type_hint", tokenTypeHint); + } + requestInfo + .clientAuthParams() + .forEach((key, value) -> form.param(key, value.apply(workflow, task, model))); + + return builder.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED)); + } + + private Invocation.Builder commonHeaders( + WebTarget target, WorkflowContext workflow, TaskContext task, WorkflowModel model) { + 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, model); + if (headerValue != null) { + builder.header(entry.getKey(), headerValue); + } + } + return builder; + } + + private URI endpoint( + Optional> resolver, + String name, + WorkflowContext workflow, + TaskContext task, + WorkflowModel model) { + return resolver + .map(r -> r.apply(workflow, task, model)) + .orElseThrow( + () -> + new UnsupportedOperationException( + "No " + name + " endpoint is configured for this provider")); + } + + private void ensureSuccessful(Response response, TaskContext task, String action) { + int status = response.getStatus(); + if (status < 200 || status >= 300) { + throw new WorkflowException( + WorkflowError.communication( + status, + task, + "Failed to " + action + ": HTTP " + status + " — " + readError(response)) + .build()); + } + } + + private static String readError(Response response) { + return response.hasEntity() ? response.readEntity(String.class) : ""; + } + + private T execute(TaskContext task, Supplier call) { + try { + return call.get(); + } catch (ResponseProcessingException e) { + throw new WorkflowException( + WorkflowError.communication( + e.getResponse().getStatus(), + task, + "Failed to process response: " + e.getMessage()) + .build(), + e); + } catch (ProcessingException e) { + throw new WorkflowException( + WorkflowError.communication( + task, "Failed to connect or process request: " + e.getMessage()) + .build(), + e); + } + } } diff --git a/impl/http/src/test/java/io/serverlessworkflow/impl/executors/http/auth/JaxRSAccessTokenProviderTest.java b/impl/http/src/test/java/io/serverlessworkflow/impl/executors/http/auth/JaxRSAccessTokenProviderTest.java new file mode 100644 index 000000000..7863f3a30 --- /dev/null +++ b/impl/http/src/test/java/io/serverlessworkflow/impl/executors/http/auth/JaxRSAccessTokenProviderTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2020-Present The Serverless 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 io.serverlessworkflow.impl.executors.http.auth; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowValueResolver; +import io.serverlessworkflow.impl.auth.AccessTokenProvider; +import io.serverlessworkflow.impl.auth.HttpRequestInfo; +import io.serverlessworkflow.impl.auth.JWTConverter; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +class JaxRSAccessTokenProviderTest { + + private MockWebServer server; + private WorkflowContext workflow; + private TaskContext task; + + @BeforeEach + void setUp() throws Exception { + server = new MockWebServer(); + server.start(); + workflow = mock(WorkflowContext.class, RETURNS_DEEP_STUBS); + when(workflow.definition().application().additionalObject(any(), any(), any())) + .thenReturn(Optional.empty()); + task = mock(TaskContext.class, RETURNS_DEEP_STUBS); + } + + @AfterEach + void tearDown() throws Exception { + server.shutdown(); + } + + private AccessTokenProvider provider() { + JWTConverter converter = token -> null; + HttpRequestInfo requestInfo = + new HttpRequestInfo( + Map.of(), + Map.of(), + Map.of( + "client_id", value("serverless-workflow"), + "client_secret", value("top-secret")), + value(server.url("/oauth2/token").uri()), + Optional.of(value(server.url("/oauth2/revoke").uri())), + Optional.of(value(server.url("/oauth2/introspect").uri())), + "client_credentials", + "application/x-www-form-urlencoded"); + return new JaxRSAccessTokenProviderFactory().build(requestInfo, List.of(), converter); + } + + private static WorkflowValueResolver value(T value) { + return (w, t, m) -> value; + } +} diff --git a/impl/test/src/test/java/io/serverlessworkflow/impl/auth/OAuthRequestBuilderTest.java b/impl/test/src/test/java/io/serverlessworkflow/impl/auth/OAuthRequestBuilderTest.java new file mode 100644 index 000000000..2992032f4 --- /dev/null +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/auth/OAuthRequestBuilderTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2020-Present The Serverless 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 io.serverlessworkflow.impl.auth; + +import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.serverlessworkflow.api.types.OAuth2AuthenticationDataClient; +import io.serverlessworkflow.api.types.OAuth2AuthenticationPropertiesEndpoints; +import io.serverlessworkflow.api.types.OAuth2ConnectAuthenticationProperties; +import io.serverlessworkflow.api.types.UriTemplate; +import io.serverlessworkflow.impl.WorkflowApplication; +import java.net.URI; +import org.junit.jupiter.api.Test; + +class OAuthRequestBuilderTest { + + private static final URI AUTHORITY = URI.create("http://localhost:8888/realms/test-realm"); + + @Test + void resolvesConfiguredEndpointsAndNormalizesLeadingSlash() { + OAuth2ConnectAuthenticationProperties props = baseProperties(); + props.withEndpoints( + new OAuth2AuthenticationPropertiesEndpoints() + .withToken("protocol/openid-connect/token") + // Leading slash must be stripped before being concatenated to the authority. + .withRevocation("/protocol/openid-connect/revoke") + .withIntrospection("protocol/openid-connect/introspect")); + + HttpRequestInfo info = build(props); + + assertEquals( + URI.create("http://localhost:8888/realms/test-realm/protocol/openid-connect/token"), + info.uri().apply(null, null, null)); + assertTrue(info.revocationUri().isPresent()); + assertEquals( + URI.create("http://localhost:8888/realms/test-realm/protocol/openid-connect/revoke"), + info.revocationUri().get().apply(null, null, null)); + assertTrue(info.introspectionUri().isPresent()); + assertEquals( + URI.create("http://localhost:8888/realms/test-realm/protocol/openid-connect/introspect"), + info.introspectionUri().get().apply(null, null, null)); + } + + @Test + void appliesDefaultTokenPathAndLeavesManagementEndpointsUnconfigured() { + HttpRequestInfo info = build(baseProperties()); + + assertEquals( + URI.create("http://localhost:8888/realms/test-realm/oauth2/token"), + info.uri().apply(null, null, null)); + assertFalse(info.revocationUri().isPresent()); + assertFalse(info.introspectionUri().isPresent()); + } + + private static OAuth2ConnectAuthenticationProperties baseProperties() { + OAuth2ConnectAuthenticationProperties props = new OAuth2ConnectAuthenticationProperties(); + props.withAuthority(new UriTemplate().withLiteralUri(AUTHORITY)); + props.withGrant(CLIENT_CREDENTIALS); + props.withClient( + new OAuth2AuthenticationDataClient().withId("serverless-workflow").withSecret("secret")); + return props; + } + + private static HttpRequestInfo build(OAuth2ConnectAuthenticationProperties props) { + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + return new OAuthRequestBuilder(app).apply(props); + } + } +} diff --git a/impl/test/src/test/java/io/serverlessworkflow/impl/test/OAuthHTTPWorkflowDefinitionTest.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/OAuthHTTPWorkflowDefinitionTest.java index 541d04c10..a6adf32c0 100644 --- a/impl/test/src/test/java/io/serverlessworkflow/impl/test/OAuthHTTPWorkflowDefinitionTest.java +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/OAuthHTTPWorkflowDefinitionTest.java @@ -18,12 +18,15 @@ import static io.serverlessworkflow.api.WorkflowReader.readWorkflowFromClasspath; import static io.serverlessworkflow.impl.test.AccessTokenProvider.fakeAccessToken; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.databind.ObjectMapper; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.WorkflowApplication; import java.io.IOException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.Map; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -150,7 +153,7 @@ public void testOAuthClientSecretPostWithArgsWorkflowExecution() throws Exceptio Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT", + "clientSecret", "dummy-secret-for-tests", "username", "serverless-workflow-test", "password", "serverless-workflow-test"); @@ -200,7 +203,7 @@ public void testOAuthClientSecretPostWithArgsNoEndPointWorkflowExecution() throw Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT", + "clientSecret", "dummy-secret-for-tests", "username", "serverless-workflow-test", "password", "serverless-workflow-test"); @@ -249,7 +252,7 @@ public void testOAuthClientSecretPostWithArgsAllGrantsWorkflowExecution() throws Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT", + "clientSecret", "dummy-secret-for-tests", "username", "serverless-workflow-test", "password", "serverless-workflow-test", "openidScope", "openidScope", @@ -317,7 +320,51 @@ public void testOAuthClientSecretPostClientCredentialsWorkflowExecution() throws String tokenRequestBody = tokenRequest.getBody().readUtf8(); assertTrue(tokenRequestBody.contains("grant_type=client_credentials")); assertTrue(tokenRequestBody.contains("client_id=serverless-workflow")); - assertTrue(tokenRequestBody.contains("secret=D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + assertTrue(tokenRequestBody.contains("client_secret=dummy-secret-for-tests")); + + RecordedRequest petRequest = apiServer.takeRequest(); + assertEquals("GET", petRequest.getMethod()); + assertEquals("/hello", petRequest.getPath()); + assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); + } + + @Test + public void testOAuthClientSecretPostClientCredentialsAllEndpointsWorkflowExecution() + throws Exception { + String jwt = fakeAccessToken(); + + String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); + + authServer.enqueue( + new MockResponse() + .setBody(tokenResponse) + .setHeader("Content-Type", "application/json") + .setResponseCode(200)); + + apiServer.enqueue( + new MockResponse() + .setBody(RESPONSE) + .setHeader("Content-Type", "application/json") + .setResponseCode(200)); + + Workflow workflow = + readWorkflowFromClasspath( + "workflows-samples/oauth2/oAuthClientSecretPostClientCredentialsAllEndpointsHttpCall.yaml"); + Map result = + app.workflowDefinition(workflow).instance(Map.of()).start().get().asMap().orElseThrow(); + + assertTrue(result.containsKey("message")); + assertTrue(result.get("message").toString().contains("Hello World")); + + RecordedRequest tokenRequest = authServer.takeRequest(); + assertEquals("POST", tokenRequest.getMethod()); + assertEquals("/realms/test-realm/protocol/openid-connect/token", tokenRequest.getPath()); + assertEquals("application/x-www-form-urlencoded", tokenRequest.getHeader("Content-Type")); + + String tokenRequestBody = tokenRequest.getBody().readUtf8(); + assertTrue(tokenRequestBody.contains("grant_type=client_credentials")); + assertTrue(tokenRequestBody.contains("client_id=serverless-workflow")); + assertTrue(tokenRequestBody.contains("client_secret=dummy-secret-for-tests")); RecordedRequest petRequest = apiServer.takeRequest(); assertEquals("GET", petRequest.getMethod()); @@ -349,7 +396,7 @@ public void testOAuthClientSecretPostClientCredentialsParamsWorkflowExecution() Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT"); + "clientSecret", "dummy-secret-for-tests"); Map result = app.workflowDefinition(workflow).instance(params).start().get().asMap().orElseThrow(); @@ -365,7 +412,7 @@ public void testOAuthClientSecretPostClientCredentialsParamsWorkflowExecution() String tokenRequestBody = tokenRequest.getBody().readUtf8(); assertTrue(tokenRequestBody.contains("grant_type=client_credentials")); assertTrue(tokenRequestBody.contains("client_id=serverless-workflow")); - assertTrue(tokenRequestBody.contains("secret=D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + assertTrue(tokenRequestBody.contains("client_secret=dummy-secret-for-tests")); RecordedRequest petRequest = apiServer.takeRequest(); assertEquals("GET", petRequest.getMethod()); @@ -398,7 +445,7 @@ public void testOAuthClientSecretPostClientCredentialsParamsNoEndpointWorkflowEx Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT"); + "clientSecret", "dummy-secret-for-tests"); Map result = app.workflowDefinition(workflow).instance(params).start().get().asMap().orElseThrow(); @@ -414,7 +461,7 @@ public void testOAuthClientSecretPostClientCredentialsParamsNoEndpointWorkflowEx String tokenRequestBody = tokenRequest.getBody().readUtf8(); assertTrue(tokenRequestBody.contains("grant_type=client_credentials")); assertTrue(tokenRequestBody.contains("client_id=serverless-workflow")); - assertTrue(tokenRequestBody.contains("secret=D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + assertTrue(tokenRequestBody.contains("client_secret=dummy-secret-for-tests")); RecordedRequest petRequest = apiServer.takeRequest(); assertEquals("GET", petRequest.getMethod()); @@ -462,7 +509,7 @@ public void testOAuthJSONPasswordWorkflowExecution() throws Exception { asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); assertTrue( asJson.containsKey("username") @@ -499,7 +546,7 @@ public void testOAuthJSONWithArgsWorkflowExecution() throws Exception { Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT", + "clientSecret", "dummy-secret-for-tests", "username", "serverless-workflow-test", "password", "serverless-workflow-test"); @@ -522,7 +569,7 @@ public void testOAuthJSONWithArgsWorkflowExecution() throws Exception { asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); assertTrue( asJson.containsKey("username") && asJson.get("username").equals("serverless-workflow-test")); @@ -559,7 +606,7 @@ public void testOAuthJSONWithArgsNoEndPointWorkflowExecution() throws Exception Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT", + "clientSecret", "dummy-secret-for-tests", "username", "serverless-workflow-test", "password", "serverless-workflow-test"); @@ -581,7 +628,7 @@ public void testOAuthJSONWithArgsNoEndPointWorkflowExecution() throws Exception asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); assertTrue( asJson.containsKey("username") && asJson.get("username").equals("serverless-workflow-test")); @@ -618,7 +665,7 @@ public void testOAuthJSONWithArgsAllGrantsWorkflowExecution() throws Exception { Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT", + "clientSecret", "dummy-secret-for-tests", "username", "serverless-workflow-test", "password", "serverless-workflow-test", "openidScope", "openidScope", @@ -643,7 +690,7 @@ public void testOAuthJSONWithArgsAllGrantsWorkflowExecution() throws Exception { asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); assertTrue( asJson.containsKey("username") && asJson.get("username").equals("serverless-workflow-test")); @@ -707,7 +754,7 @@ public void testOAuthJSONClientCredentialsWorkflowExecution() throws Exception { asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); RecordedRequest petRequest = apiServer.takeRequest(); assertEquals("GET", petRequest.getMethod()); @@ -739,7 +786,7 @@ public void testOAuthJSONClientCredentialsParamsWorkflowExecution() throws Excep Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT"); + "clientSecret", "dummy-secret-for-tests"); Map result = app.workflowDefinition(workflow).instance(params).start().get().asMap().orElseThrow(); @@ -760,7 +807,7 @@ public void testOAuthJSONClientCredentialsParamsWorkflowExecution() throws Excep asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); RecordedRequest petRequest = apiServer.takeRequest(); assertEquals("GET", petRequest.getMethod()); @@ -792,7 +839,7 @@ public void testOAuthJSONClientCredentialsParamsNoEndpointWorkflowExecution() th Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT"); + "clientSecret", "dummy-secret-for-tests"); Map result = app.workflowDefinition(workflow).instance(params).start().get().asMap().orElseThrow(); @@ -813,11 +860,97 @@ public void testOAuthJSONClientCredentialsParamsNoEndpointWorkflowExecution() th asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); RecordedRequest petRequest = apiServer.takeRequest(); assertEquals("GET", petRequest.getMethod()); assertEquals("/hello", petRequest.getPath()); assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); } + + @Test + public void testOAuthClientSecretJwtClientCredentialsWorkflowExecution() throws Exception { + String assertion = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.client-secret-jwt-assertion.signature"; + String tokenRequestBody = + runOAuthTokenRequestWorkflow( + "workflows-samples/oauth2/oAuthClientSecretJwtClientCredentialsHttpCall.yaml"); + + assertTrue(tokenRequestBody.contains("grant_type=client_credentials")); + assertTrue(tokenRequestBody.contains("client_id=serverless-workflow")); + assertTrue( + tokenRequestBody.contains( + "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer")); + assertTrue(tokenRequestBody.contains("client_assertion=" + assertion)); + assertFalse(tokenRequestBody.contains("client_secret=")); + } + + @Test + public void testOAuthPrivateKeyJwtClientCredentialsWorkflowExecution() throws Exception { + String assertion = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.private-key-jwt-assertion.signature"; + String tokenRequestBody = + runOAuthTokenRequestWorkflow( + "workflows-samples/oauth2/oAuthPrivateKeyJwtClientCredentialsHttpCall.yaml"); + + assertTrue(tokenRequestBody.contains("grant_type=client_credentials")); + assertTrue(tokenRequestBody.contains("client_id=serverless-workflow")); + assertTrue( + tokenRequestBody.contains( + "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer")); + assertTrue(tokenRequestBody.contains("client_assertion=" + assertion)); + assertFalse(tokenRequestBody.contains("client_secret=")); + } + + @Test + public void testOAuthTokenExchangeSubjectActorWorkflowExecution() throws Exception { + String tokenRequestBody = + runOAuthTokenRequestWorkflow( + "workflows-samples/oauth2/oAuthClientSecretPostTokenExchangeHttpCall.yaml"); + + assertTrue( + tokenRequestBody.contains("grant_type=urn:ietf:params:oauth:grant-type:token-exchange")); + assertTrue(tokenRequestBody.contains("subject_token=subject-token-value")); + assertTrue( + tokenRequestBody.contains( + "subject_token_type=urn:ietf:params:oauth:token-type:access_token")); + assertTrue(tokenRequestBody.contains("actor_token=actor-token-value")); + assertTrue( + tokenRequestBody.contains( + "actor_token_type=urn:ietf:params:oauth:token-type:access_token")); + } + + private String runOAuthTokenRequestWorkflow(String workflowResource) throws Exception { + String jwt = fakeAccessToken(); + String tokenResponse = TOKEN_RESPONSE_TEMPLATE.formatted(jwt); + + authServer.enqueue( + new MockResponse() + .setBody(tokenResponse) + .setHeader("Content-Type", "application/json") + .setResponseCode(200)); + + apiServer.enqueue( + new MockResponse() + .setBody(RESPONSE) + .setHeader("Content-Type", "application/json") + .setResponseCode(200)); + + Workflow workflow = readWorkflowFromClasspath(workflowResource); + Map result = + app.workflowDefinition(workflow).instance(Map.of()).start().get().asMap().orElseThrow(); + + assertTrue(result.containsKey("message")); + assertTrue(result.get("message").toString().contains("Hello World")); + + RecordedRequest tokenRequest = authServer.takeRequest(); + assertEquals("POST", tokenRequest.getMethod()); + assertEquals("/realms/test-realm/protocol/openid-connect/token", tokenRequest.getPath()); + assertEquals("application/x-www-form-urlencoded", tokenRequest.getHeader("Content-Type")); + + RecordedRequest petRequest = apiServer.takeRequest(); + assertEquals("GET", petRequest.getMethod()); + assertEquals("/hello", petRequest.getPath()); + assertEquals("Bearer " + jwt, petRequest.getHeader("Authorization")); + + return URLDecoder.decode(tokenRequest.getBody().readUtf8(), StandardCharsets.UTF_8); + } } diff --git a/impl/test/src/test/java/io/serverlessworkflow/impl/test/OpenIDCHTTPWorkflowDefinitionTest.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/OpenIDCHTTPWorkflowDefinitionTest.java index 66cc17cd1..878004950 100644 --- a/impl/test/src/test/java/io/serverlessworkflow/impl/test/OpenIDCHTTPWorkflowDefinitionTest.java +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/OpenIDCHTTPWorkflowDefinitionTest.java @@ -121,7 +121,7 @@ public void testOpenIDCClientSecretPostPasswordWorkflowExecution() throws Except assertTrue(tokenRequestBody.contains("username=serverless-workflow-test")); assertTrue(tokenRequestBody.contains("password=serverless-workflow-test")); assertTrue(tokenRequestBody.contains("client_id=serverless-workflow")); - assertTrue(tokenRequestBody.contains("client_secret=D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + assertTrue(tokenRequestBody.contains("client_secret=dummy-secret-for-tests")); assertTrue(tokenRequestBody.contains("scope=openid")); RecordedRequest petRequest = apiServer.takeRequest(); @@ -153,7 +153,7 @@ public void testOpenIDCClientSecretPostWithArgsWorkflowExecution() throws Except Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT", + "clientSecret", "dummy-secret-for-tests", "username", "serverless-workflow-test", "password", "serverless-workflow-test"); @@ -173,7 +173,7 @@ public void testOpenIDCClientSecretPostWithArgsWorkflowExecution() throws Except assertTrue(tokenRequestBody.contains("username=serverless-workflow-test")); assertTrue(tokenRequestBody.contains("password=serverless-workflow-test")); assertTrue(tokenRequestBody.contains("client_id=serverless-workflow")); - assertTrue(tokenRequestBody.contains("client_secret=D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + assertTrue(tokenRequestBody.contains("client_secret=dummy-secret-for-tests")); RecordedRequest petRequest = apiServer.takeRequest(); assertEquals("GET", petRequest.getMethod()); @@ -204,7 +204,7 @@ public void testOpenIDCClientSecretPostWithArgsAllGrantsWorkflowExecution() thro Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT", + "clientSecret", "dummy-secret-for-tests", "username", "serverless-workflow-test", "password", "serverless-workflow-test"); @@ -263,7 +263,7 @@ public void testOpenIDCClientSecretPostClientCredentialsParamsWorkflowExecution( Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT"); + "clientSecret", "dummy-secret-for-tests"); Map result = app.workflowDefinition(workflow).instance(params).start().get().asMap().orElseThrow(); @@ -279,7 +279,7 @@ public void testOpenIDCClientSecretPostClientCredentialsParamsWorkflowExecution( String tokenRequestBody = tokenRequest.getBody().readUtf8(); assertTrue(tokenRequestBody.contains("grant_type=client_credentials")); assertTrue(tokenRequestBody.contains("client_id=serverless-workflow")); - assertTrue(tokenRequestBody.contains("secret=D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + assertTrue(tokenRequestBody.contains("client_secret=dummy-secret-for-tests")); RecordedRequest petRequest = apiServer.takeRequest(); assertEquals("GET", petRequest.getMethod()); @@ -312,7 +312,7 @@ public void testOpenIDCClientSecretPostClientCredentialsParamsNoEndpointWorkflow Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT"); + "clientSecret", "dummy-secret-for-tests"); Map result = app.workflowDefinition(workflow).instance(params).start().get().asMap().orElseThrow(); @@ -327,7 +327,7 @@ public void testOpenIDCClientSecretPostClientCredentialsParamsNoEndpointWorkflow String tokenRequestBody = tokenRequest.getBody().readUtf8(); assertTrue(tokenRequestBody.contains("grant_type=client_credentials")); assertTrue(tokenRequestBody.contains("client_id=serverless-workflow")); - assertTrue(tokenRequestBody.contains("secret=D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + assertTrue(tokenRequestBody.contains("client_secret=dummy-secret-for-tests")); RecordedRequest petRequest = apiServer.takeRequest(); assertEquals("GET", petRequest.getMethod()); @@ -375,7 +375,7 @@ public void testOpenIDCJSONPasswordWorkflowExecution() throws Exception { asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); assertTrue( asJson.containsKey("username") @@ -412,7 +412,7 @@ public void testOpenIDCJSONWithArgsWorkflowExecution() throws Exception { Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT", + "clientSecret", "dummy-secret-for-tests", "username", "serverless-workflow-test", "password", "serverless-workflow-test"); @@ -435,7 +435,7 @@ public void testOpenIDCJSONWithArgsWorkflowExecution() throws Exception { asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); assertTrue( asJson.containsKey("username") && asJson.get("username").equals("serverless-workflow-test")); @@ -472,7 +472,7 @@ public void testOpenIDCJSONWithArgsNoEndPointWorkflowExecution() throws Exceptio Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT", + "clientSecret", "dummy-secret-for-tests", "username", "serverless-workflow-test", "password", "serverless-workflow-test"); @@ -494,7 +494,7 @@ public void testOpenIDCJSONWithArgsNoEndPointWorkflowExecution() throws Exceptio asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); assertTrue( asJson.containsKey("username") && asJson.get("username").equals("serverless-workflow-test")); @@ -531,7 +531,7 @@ public void testOpenIDCJSONWithArgsAllGrantsWorkflowExecution() throws Exception Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT", + "clientSecret", "dummy-secret-for-tests", "username", "serverless-workflow-test", "password", "serverless-workflow-test", "openidScope", "openidScope", @@ -556,7 +556,7 @@ public void testOpenIDCJSONWithArgsAllGrantsWorkflowExecution() throws Exception asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); assertTrue( asJson.containsKey("username") && asJson.get("username").equals("serverless-workflow-test")); @@ -620,7 +620,7 @@ public void testOpenIDCJSONClientCredentialsWorkflowExecution() throws Exception asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); RecordedRequest petRequest = apiServer.takeRequest(); assertEquals("GET", petRequest.getMethod()); @@ -652,7 +652,7 @@ public void testOpenIDCJSONClientCredentialsParamsWorkflowExecution() throws Exc Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT"); + "clientSecret", "dummy-secret-for-tests"); Map result = app.workflowDefinition(workflow).instance(params).start().get().asMap().orElseThrow(); @@ -673,7 +673,7 @@ public void testOpenIDCJSONClientCredentialsParamsWorkflowExecution() throws Exc asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); RecordedRequest petRequest = apiServer.takeRequest(); assertEquals("GET", petRequest.getMethod()); @@ -705,7 +705,7 @@ public void testOpenIDCJSONClientCredentialsParamsNoEndpointWorkflowExecution() Map params = Map.of( "clientId", "serverless-workflow", - "clientSecret", "D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT"); + "clientSecret", "dummy-secret-for-tests"); Map result = app.workflowDefinition(workflow).instance(params).start().get().asMap().orElseThrow(); @@ -726,7 +726,7 @@ public void testOpenIDCJSONClientCredentialsParamsNoEndpointWorkflowExecution() asJson.containsKey("client_id") && asJson.get("client_id").equals("serverless-workflow")); assertTrue( asJson.containsKey("client_secret") - && asJson.get("client_secret").equals("D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT")); + && asJson.get("client_secret").equals("dummy-secret-for-tests")); RecordedRequest petRequest = apiServer.takeRequest(); assertEquals("GET", petRequest.getMethod()); diff --git a/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretJwtClientCredentialsHttpCall.yaml b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretJwtClientCredentialsHttpCall.yaml new file mode 100644 index 000000000..a2ddb9c8d --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretJwtClientCredentialsHttpCall.yaml @@ -0,0 +1,24 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: oauth2-authentication-client-secret-jwt-client-credentials + version: '0.0.1' +do: + - getPet: + call: http + with: + method: get + endpoint: + uri: http://localhost:8081/hello + authentication: + oauth2: + authority: http://localhost:8888/realms/test-realm + endpoints: + token: protocol/openid-connect/token + grant: client_credentials + client: + id: serverless-workflow + authentication: client_secret_jwt + assertion: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.client-secret-jwt-assertion.signature + issuers: + - http://localhost:8888/realms/test-realm diff --git a/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostClientCredentialsAllEndpointsHttpCall.yaml b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostClientCredentialsAllEndpointsHttpCall.yaml new file mode 100644 index 000000000..10e13bc54 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostClientCredentialsAllEndpointsHttpCall.yaml @@ -0,0 +1,25 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: oauth2-authentication-secret-client-credentials-all-endpoints + version: '0.0.1' +do: + - getPet: + call: http + with: + method: get + endpoint: + uri: http://localhost:8081/hello + authentication: + oauth2: + authority: http://localhost:8888/realms/test-realm + endpoints: + token: protocol/openid-connect/token + revocation: protocol/openid-connect/revoke + introspection: protocol/openid-connect/introspect + grant: client_credentials + client: + id: serverless-workflow + secret: dummy-secret-for-tests + issuers: + - http://localhost:8888/realms/test-realm diff --git a/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostClientCredentialsHttpCall.yaml b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostClientCredentialsHttpCall.yaml index 7b33d5099..23d22b83d 100644 --- a/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostClientCredentialsHttpCall.yaml +++ b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostClientCredentialsHttpCall.yaml @@ -18,6 +18,6 @@ do: grant: client_credentials client: id: serverless-workflow - secret: D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT + secret: dummy-secret-for-tests issuers: - http://localhost:8888/realms/test-realm diff --git a/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostPasswordHttpCall.yaml b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostPasswordHttpCall.yaml index be57f2000..e83286b18 100644 --- a/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostPasswordHttpCall.yaml +++ b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostPasswordHttpCall.yaml @@ -18,7 +18,7 @@ do: grant: password client: id: serverless-workflow - secret: D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT + secret: dummy-secret-for-tests username: serverless-workflow-test password: serverless-workflow-test issuers: diff --git a/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostTokenExchangeHttpCall.yaml b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostTokenExchangeHttpCall.yaml new file mode 100644 index 000000000..6ed38d811 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthClientSecretPostTokenExchangeHttpCall.yaml @@ -0,0 +1,29 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: oauth2-authentication-token-exchange + version: '0.0.1' +do: + - getPet: + call: http + with: + method: get + endpoint: + uri: http://localhost:8081/hello + authentication: + oauth2: + authority: http://localhost:8888/realms/test-realm + endpoints: + token: protocol/openid-connect/token + grant: urn:ietf:params:oauth:grant-type:token-exchange + client: + id: serverless-workflow + secret: dummy-secret-for-tests + subject: + token: subject-token-value + type: urn:ietf:params:oauth:token-type:access_token + actor: + token: actor-token-value + type: urn:ietf:params:oauth:token-type:access_token + issuers: + - http://localhost:8888/realms/test-realm diff --git a/impl/test/src/test/resources/workflows-samples/oauth2/oAuthJSONClientCredentialsHttpCall.yaml b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthJSONClientCredentialsHttpCall.yaml index 6b0cf188e..05a954a0b 100644 --- a/impl/test/src/test/resources/workflows-samples/oauth2/oAuthJSONClientCredentialsHttpCall.yaml +++ b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthJSONClientCredentialsHttpCall.yaml @@ -21,6 +21,6 @@ do: encoding: application/json client: id: serverless-workflow - secret: D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT + secret: dummy-secret-for-tests issuers: - http://localhost:8888/realms/test-realm diff --git a/impl/test/src/test/resources/workflows-samples/oauth2/oAuthJSONPasswordHttpCall.yaml b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthJSONPasswordHttpCall.yaml index 7d7b32125..3c7911daa 100644 --- a/impl/test/src/test/resources/workflows-samples/oauth2/oAuthJSONPasswordHttpCall.yaml +++ b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthJSONPasswordHttpCall.yaml @@ -20,7 +20,7 @@ do: encoding: application/json client: id: serverless-workflow - secret: D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT + secret: dummy-secret-for-tests username: serverless-workflow-test password: serverless-workflow-test issuers: diff --git a/impl/test/src/test/resources/workflows-samples/oauth2/oAuthPrivateKeyJwtClientCredentialsHttpCall.yaml b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthPrivateKeyJwtClientCredentialsHttpCall.yaml new file mode 100644 index 000000000..dc69c6df0 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/oauth2/oAuthPrivateKeyJwtClientCredentialsHttpCall.yaml @@ -0,0 +1,24 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: oauth2-authentication-private-key-jwt-client-credentials + version: '0.0.1' +do: + - getPet: + call: http + with: + method: get + endpoint: + uri: http://localhost:8081/hello + authentication: + oauth2: + authority: http://localhost:8888/realms/test-realm + endpoints: + token: protocol/openid-connect/token + grant: client_credentials + client: + id: serverless-workflow + authentication: private_key_jwt + assertion: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.private-key-jwt-assertion.signature + issuers: + - http://localhost:8888/realms/test-realm diff --git a/impl/test/src/test/resources/workflows-samples/openid/openidcClientSecretPostPasswordHttpCall.yaml b/impl/test/src/test/resources/workflows-samples/openid/openidcClientSecretPostPasswordHttpCall.yaml index 383e84a5d..31286a95d 100644 --- a/impl/test/src/test/resources/workflows-samples/openid/openidcClientSecretPostPasswordHttpCall.yaml +++ b/impl/test/src/test/resources/workflows-samples/openid/openidcClientSecretPostPasswordHttpCall.yaml @@ -16,6 +16,6 @@ do: grant: password client: id: serverless-workflow - secret: D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT + secret: dummy-secret-for-tests username: serverless-workflow-test password: serverless-workflow-test diff --git a/impl/test/src/test/resources/workflows-samples/openid/openidcJSONClientCredentialsHttpCall.yaml b/impl/test/src/test/resources/workflows-samples/openid/openidcJSONClientCredentialsHttpCall.yaml index d3aa0c2e9..a077040c7 100644 --- a/impl/test/src/test/resources/workflows-samples/openid/openidcJSONClientCredentialsHttpCall.yaml +++ b/impl/test/src/test/resources/workflows-samples/openid/openidcJSONClientCredentialsHttpCall.yaml @@ -18,6 +18,6 @@ do: encoding: application/json client: id: serverless-workflow - secret: D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT + secret: dummy-secret-for-tests issuers: - http://localhost:8888/realms/test-realm diff --git a/impl/test/src/test/resources/workflows-samples/openid/openidcJSONPasswordHttpCall.yaml b/impl/test/src/test/resources/workflows-samples/openid/openidcJSONPasswordHttpCall.yaml index 90cac7a32..033f1712d 100644 --- a/impl/test/src/test/resources/workflows-samples/openid/openidcJSONPasswordHttpCall.yaml +++ b/impl/test/src/test/resources/workflows-samples/openid/openidcJSONPasswordHttpCall.yaml @@ -18,7 +18,7 @@ do: encoding: application/json client: id: serverless-workflow - secret: D0ACXCUKOUrL5YL7j6RQWplMaSjPB8MT + secret: dummy-secret-for-tests username: serverless-workflow-test password: serverless-workflow-test issuers: