diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java index 413bfbe80..2cbca0283 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -35,6 +35,7 @@ import io.serverlessworkflow.impl.resources.DefaultResourceLoaderFactory; import io.serverlessworkflow.impl.resources.ExternalResourceHandler; import io.serverlessworkflow.impl.resources.ResourceLoaderFactory; +import io.serverlessworkflow.impl.resources.URITemplateResolver; import io.serverlessworkflow.impl.scheduler.DefaultWorkflowScheduler; import io.serverlessworkflow.impl.scheduler.WorkflowScheduler; import io.serverlessworkflow.impl.schema.SchemaValidator; @@ -73,6 +74,7 @@ public class WorkflowApplication implements AutoCloseable { private final ConfigManager configManager; private final SecretManager secretManager; private final SchedulerListener schedulerListener; + private final Optional templateResolver; private WorkflowApplication(Builder builder) { this.taskFactory = builder.taskFactory; @@ -94,6 +96,7 @@ private WorkflowApplication(Builder builder) { this.additionalObjects = builder.additionalObjects; this.configManager = builder.configManager; this.secretManager = builder.secretManager; + this.templateResolver = builder.templateResolver; } public TaskExecutorFactory taskFactory() { @@ -173,6 +176,7 @@ public SchemaValidator getValidator(SchemaInline inline) { private SecretManager secretManager; private ConfigManager configManager; private SchedulerListener schedulerListener; + private Optional templateResolver; private Builder() {} @@ -325,6 +329,7 @@ public WorkflowApplication build() { .findFirst() .orElseGet(() -> new ConfigSecretManager(configManager)); } + templateResolver = ServiceLoader.load(URITemplateResolver.class).findFirst(); return new WorkflowApplication(this); } } @@ -398,6 +403,10 @@ SchedulerListener schedulerListener() { return schedulerListener; } + public Optional templateResolver() { + return templateResolver; + } + public Optional additionalObject( String name, WorkflowContext workflowContext, TaskContext taskContext) { return Optional.ofNullable(additionalObjects.get(name)) diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java index 6fef5a859..4ebfcc8e3 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java @@ -20,8 +20,10 @@ import io.serverlessworkflow.api.types.InputFrom; import io.serverlessworkflow.api.types.OutputAs; import io.serverlessworkflow.api.types.SchemaUnion; +import io.serverlessworkflow.api.types.SecretBasedAuthenticationPolicy; import io.serverlessworkflow.api.types.TimeoutAfter; import io.serverlessworkflow.api.types.UriTemplate; +import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.expressions.ExpressionDescriptor; import io.serverlessworkflow.impl.expressions.ExpressionUtils; import io.serverlessworkflow.impl.resources.ResourceLoader; @@ -200,4 +202,45 @@ public static WorkflowValueResolver fromTimeoutAfter( return (w, t, f) -> Duration.ZERO; } } + + public static final String secretProp(WorkflowContext context, String secretName, String prop) { + return (String) secret(context, secretName).get(prop); + } + + public static final Map secret(WorkflowContext context, String secretName) { + return context.definition().application().secretManager().secret(secretName); + } + + public static final String checkSecret( + Workflow workflow, SecretBasedAuthenticationPolicy secretPolicy) { + String secretName = secretPolicy.getUse(); + return workflow.getUse().getSecrets().stream() + .filter(s -> s.equals(secretName)) + .findAny() + .orElseThrow(() -> new IllegalStateException("Secret " + secretName + " does not exist")); + } + + public static URI concatURI(URI uri, String pathToAppend) { + return uri.getPath().endsWith("/") + ? uri.resolve(pathToAppend) + : URI.create( + uri.toString() + (pathToAppend.startsWith("/") ? pathToAppend : "/" + pathToAppend)); + } + + public static WorkflowValueResolver getURISupplier( + WorkflowApplication application, UriTemplate template) { + if (template.getLiteralUri() != null) { + return (w, t, n) -> template.getLiteralUri(); + } else if (template.getLiteralUriTemplate() != null) { + return (w, t, n) -> + application + .templateResolver() + .orElseThrow( + () -> + new IllegalStateException( + "Need an uri template resolver to resolve uri template")) + .resolveTemplates(template.getLiteralUriTemplate(), w, t, n); + } + throw new IllegalArgumentException("Invalid uritemplate definition " + template); + } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/config/ConfigSecretManager.java b/impl/core/src/main/java/io/serverlessworkflow/impl/config/ConfigSecretManager.java index a720e447a..b9e1b6aeb 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/config/ConfigSecretManager.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/config/ConfigSecretManager.java @@ -23,19 +23,19 @@ public class ConfigSecretManager implements SecretManager { private final ConfigManager configManager; - private Map> secretMap = new ConcurrentHashMap<>(); + private Map> secretMap = new ConcurrentHashMap<>(); public ConfigSecretManager(ConfigManager configManager) { this.configManager = configManager; } @Override - public Map secret(String secretName) { + public Map secret(String secretName) { return secretMap.computeIfAbsent(secretName, this::buildMap); } - private Map buildMap(String secretName) { - Map map = new HashMap(); + private Map buildMap(String secretName) { + Map map = new HashMap<>(); final String prefix = secretName + "."; for (String name : configManager.names()) { if (name.startsWith(prefix)) { diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/config/SecretManager.java b/impl/core/src/main/java/io/serverlessworkflow/impl/config/SecretManager.java index aeeabaafc..4f639eac7 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/config/SecretManager.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/config/SecretManager.java @@ -20,5 +20,5 @@ @FunctionalInterface public interface SecretManager extends ServicePriority { - Map secret(String secretName); + Map secret(String secretName); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoader.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoader.java index 47c6a1c9a..e30187177 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoader.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoader.java @@ -15,10 +15,11 @@ */ package io.serverlessworkflow.impl.resources; +import static io.serverlessworkflow.impl.WorkflowUtils.getURISupplier; + import io.serverlessworkflow.api.types.Endpoint; import io.serverlessworkflow.api.types.EndpointUri; import io.serverlessworkflow.api.types.ExternalResource; -import io.serverlessworkflow.api.types.UriTemplate; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowContext; @@ -31,9 +32,7 @@ import java.time.Instant; import java.util.Map; import java.util.Optional; -import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; public class DefaultResourceLoader implements ResourceLoader { @@ -41,9 +40,6 @@ public class DefaultResourceLoader implements ResourceLoader { private final Optional workflowPath; private final WorkflowApplication application; - private final AtomicReference templateResolver = - new AtomicReference(); - private Map resourceCache = new ConcurrentHashMap<>(); protected DefaultResourceLoader(WorkflowApplication application, Path workflowPath) { @@ -51,21 +47,6 @@ protected DefaultResourceLoader(WorkflowApplication application, Path workflowPa this.workflowPath = Optional.ofNullable(workflowPath); } - private URITemplateResolver templateResolver() { - URITemplateResolver result = templateResolver.get(); - if (result == null) { - result = - ServiceLoader.load(URITemplateResolver.class) - .findFirst() - .orElseThrow( - () -> - new IllegalStateException( - "Need an uri template resolver to resolve uri template")); - templateResolver.set(result); - } - return result; - } - private ExternalResourceHandler fileResource(String pathStr) { Path path = Path.of(pathStr); if (path.isAbsolute()) { @@ -122,7 +103,7 @@ public WorkflowValueResolver uriSupplier(Endpoint endpoint) { if (endpoint.getEndpointConfiguration() != null) { EndpointUri uri = endpoint.getEndpointConfiguration().getUri(); if (uri.getLiteralEndpointURI() != null) { - return getURISupplier(uri.getLiteralEndpointURI()); + return getURISupplier(application, uri.getLiteralEndpointURI()); } else if (uri.getExpressionEndpointURI() != null) { return new ExpressionURISupplier( application @@ -135,21 +116,11 @@ public WorkflowValueResolver uriSupplier(Endpoint endpoint) { .expressionFactory() .resolveString(ExpressionDescriptor.from(endpoint.getRuntimeExpression()))); } else if (endpoint.getUriTemplate() != null) { - return getURISupplier(endpoint.getUriTemplate()); + return getURISupplier(application, endpoint.getUriTemplate()); } throw new IllegalArgumentException("Invalid endpoint definition " + endpoint); } - private WorkflowValueResolver getURISupplier(UriTemplate template) { - if (template.getLiteralUri() != null) { - return (w, t, n) -> template.getLiteralUri(); - } else if (template.getLiteralUriTemplate() != null) { - return (w, t, n) -> - templateResolver().resolveTemplates(template.getLiteralUriTemplate(), w, t, n); - } - throw new IllegalArgumentException("Invalid uritemplate definition " + template); - } - private class ExpressionURISupplier implements WorkflowValueResolver { private WorkflowValueResolver expr; diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AbstractAuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AbstractAuthProvider.java index e75961de2..ab5eb7ac3 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AbstractAuthProvider.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AbstractAuthProvider.java @@ -15,14 +15,12 @@ */ package io.serverlessworkflow.impl.executors.http; -import io.serverlessworkflow.api.types.SecretBasedAuthenticationPolicy; -import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowModel; import jakarta.ws.rs.client.Invocation.Builder; -public abstract class AbstractAuthProvider implements AuthProvider { +abstract class AbstractAuthProvider implements AuthProvider { private static final String AUTH_HEADER_FORMAT = "%s %s"; @@ -37,19 +35,6 @@ public Builder build( return builder; } - protected final String checkSecret( - Workflow workflow, SecretBasedAuthenticationPolicy secretPolicy) { - String secretName = secretPolicy.getUse(); - return workflow.getUse().getSecrets().stream() - .filter(s -> s.equals(secretName)) - .findAny() - .orElseThrow(() -> new IllegalStateException("Secret " + secretName + " does not exist")); - } - - protected final String find(WorkflowContext context, String secretName, String prop) { - return context.definition().application().secretManager().secret(secretName).get(prop); - } - protected abstract String authScheme(); protected abstract String authParameter( diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BasicAuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BasicAuthProvider.java index 1c824e064..f390d4b2e 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BasicAuthProvider.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BasicAuthProvider.java @@ -15,6 +15,11 @@ */ package io.serverlessworkflow.impl.executors.http; +import static io.serverlessworkflow.impl.WorkflowUtils.checkSecret; +import static io.serverlessworkflow.impl.WorkflowUtils.secretProp; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.PASSWORD; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.USER; + import io.serverlessworkflow.api.types.BasicAuthenticationPolicy; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.TaskContext; @@ -44,8 +49,8 @@ public BasicAuthProvider( } else if (authPolicy.getBasic().getBasicAuthenticationPolicySecret() != null) { String secretName = checkSecret(workflow, authPolicy.getBasic().getBasicAuthenticationPolicySecret()); - userFilter = (w, t, m) -> find(w, secretName, "username"); - passwordFilter = (w, t, m) -> find(w, secretName, "password"); + userFilter = (w, t, m) -> secretProp(w, secretName, USER); + passwordFilter = (w, t, m) -> secretProp(w, secretName, PASSWORD); } else { throw new IllegalStateException("Both secret and properties are null for authorization"); } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BearerAuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BearerAuthProvider.java index 58b673e9e..e1a6bb200 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BearerAuthProvider.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BearerAuthProvider.java @@ -15,6 +15,10 @@ */ package io.serverlessworkflow.impl.executors.http; +import static io.serverlessworkflow.impl.WorkflowUtils.checkSecret; +import static io.serverlessworkflow.impl.WorkflowUtils.secretProp; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.TOKEN; + import io.serverlessworkflow.api.types.BearerAuthenticationPolicy; import io.serverlessworkflow.api.types.BearerAuthenticationPolicyConfiguration; import io.serverlessworkflow.api.types.Workflow; @@ -39,7 +43,7 @@ public BearerAuthProvider( tokenFilter = WorkflowUtils.buildStringFilter(app, token); } else if (config.getBearerAuthenticationPolicySecret() != null) { String secretName = checkSecret(workflow, config.getBearerAuthenticationPolicySecret()); - tokenFilter = (w, t, m) -> find(w, secretName, "bearer"); + tokenFilter = (w, t, m) -> secretProp(w, secretName, TOKEN); } } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/CommonOAuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/CommonOAuthProvider.java new file mode 100644 index 000000000..6280c5f6b --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/CommonOAuthProvider.java @@ -0,0 +1,67 @@ +/* + * 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; + +import static io.serverlessworkflow.impl.WorkflowUtils.checkSecret; + +import io.serverlessworkflow.api.types.OAuth2AuthenticationData; +import io.serverlessworkflow.api.types.SecretBasedAuthenticationPolicy; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowValueResolver; +import io.serverlessworkflow.impl.executors.http.auth.requestbuilder.AccessTokenProvider; +import io.serverlessworkflow.impl.executors.http.auth.requestbuilder.AccessTokenProviderFactory; +import io.serverlessworkflow.impl.executors.http.auth.requestbuilder.AuthRequestBuilder; +import java.util.Map; + +abstract class CommonOAuthProvider extends AbstractAuthProvider { + + private final WorkflowValueResolver tokenProvider; + + protected CommonOAuthProvider(WorkflowValueResolver tokenProvider) { + this.tokenProvider = tokenProvider; + } + + @Override + protected String authParameter(WorkflowContext workflow, TaskContext task, WorkflowModel model) { + return tokenProvider.apply(workflow, task, model).validateAndGet(workflow, task, model).token(); + } + + @Override + protected String authScheme() { + return "Bearer"; + } + + protected static OAuth2AuthenticationData fillFromMap( + OAuth2AuthenticationData data, Map secretMap) { + return data; + } + + protected static WorkflowValueResolver accessToken( + Workflow workflow, + OAuth2AuthenticationData authenticationData, + SecretBasedAuthenticationPolicy secret, + AuthRequestBuilder builder) { + if (authenticationData != null) { + return AccessTokenProviderFactory.build(authenticationData, builder); + } else if (secret != null) { + return AccessTokenProviderFactory.build(checkSecret(workflow, secret), builder); + } + throw new IllegalStateException("Both policy and secret are null"); + } +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java index 1bd6428fb..420931a4b 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java @@ -47,7 +47,7 @@ public class HttpExecutor implements CallableTask { // TODO allow changing default converter private static final HttpModelConverter defaultConverter = new HttpModelConverter() {}; - private TargetSupplier targetSupplier; + private WorkflowValueResolver targetSupplier; private Optional>> headersMap; private Optional>> queryMap; private Optional authProvider; @@ -255,18 +255,15 @@ public boolean accept(Class clazz) { return clazz.equals(CallHTTP.class); } - private static TargetSupplier getTargetSupplier(WorkflowValueResolver uriSupplier) { + private static WorkflowValueResolver getTargetSupplier( + WorkflowValueResolver uriSupplier) { return (w, t, n) -> HttpClientResolver.client(w, t).target(uriSupplier.apply(w, t, n)); } - private static TargetSupplier getTargetSupplier( + private static WorkflowValueResolver getTargetSupplier( WorkflowValueResolver uriSupplier, WorkflowValueResolver pathSupplier) { return (w, t, n) -> HttpClientResolver.client(w, t) .target(uriSupplier.apply(w, t, n).resolve(pathSupplier.apply(w, t, n))); } - - private static interface TargetSupplier { - WebTarget apply(WorkflowContext workflow, TaskContext task, WorkflowModel node); - } } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OAuth2AuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OAuth2AuthProvider.java index c36d17ebf..b3de11de1 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OAuth2AuthProvider.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OAuth2AuthProvider.java @@ -16,37 +16,19 @@ package io.serverlessworkflow.impl.executors.http; import io.serverlessworkflow.api.types.OAuth2AuthenticationPolicy; -import io.serverlessworkflow.api.types.OAuth2AuthenticationPolicyConfiguration; import io.serverlessworkflow.api.types.Workflow; -import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; -import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.WorkflowModel; -import io.serverlessworkflow.impl.executors.http.auth.requestbuilder.AuthRequestBuilder; import io.serverlessworkflow.impl.executors.http.auth.requestbuilder.OAuthRequestBuilder; -public class OAuth2AuthProvider extends AbstractAuthProvider { - - private AuthRequestBuilder requestBuilder; +class OAuth2AuthProvider extends CommonOAuthProvider { public OAuth2AuthProvider( WorkflowApplication application, Workflow workflow, OAuth2AuthenticationPolicy authPolicy) { - OAuth2AuthenticationPolicyConfiguration oauth2 = authPolicy.getOauth2(); - if (oauth2.getOAuth2ConnectAuthenticationProperties() != null) { - this.requestBuilder = - new OAuthRequestBuilder(application, oauth2.getOAuth2ConnectAuthenticationProperties()); - } else if (oauth2.getOAuth2AuthenticationPolicySecret() != null) { - throw new UnsupportedOperationException("Secrets are still not supported"); - } - } - - @Override - protected String authParameter(WorkflowContext workflow, TaskContext task, WorkflowModel model) { - return requestBuilder.build(workflow, task, model).validateAndGet().token(); - } - - @Override - protected String authScheme() { - return "Bearer"; + super( + accessToken( + workflow, + authPolicy.getOauth2().getOAuth2ConnectAuthenticationProperties(), + authPolicy.getOauth2().getOAuth2AuthenticationPolicySecret(), + new OAuthRequestBuilder(application))); } } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OpenIdAuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OpenIdAuthProvider.java index deaa0e779..649708ad1 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OpenIdAuthProvider.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OpenIdAuthProvider.java @@ -16,41 +16,21 @@ package io.serverlessworkflow.impl.executors.http; import io.serverlessworkflow.api.types.OpenIdConnectAuthenticationPolicy; -import io.serverlessworkflow.api.types.OpenIdConnectAuthenticationPolicyConfiguration; import io.serverlessworkflow.api.types.Workflow; -import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; -import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.WorkflowModel; -import io.serverlessworkflow.impl.executors.http.auth.requestbuilder.AuthRequestBuilder; import io.serverlessworkflow.impl.executors.http.auth.requestbuilder.OpenIdRequestBuilder; -public class OpenIdAuthProvider extends AbstractAuthProvider { - - private AuthRequestBuilder requestBuilder; +class OpenIdAuthProvider extends CommonOAuthProvider { public OpenIdAuthProvider( WorkflowApplication application, Workflow workflow, OpenIdConnectAuthenticationPolicy authPolicy) { - OpenIdConnectAuthenticationPolicyConfiguration configuration = authPolicy.getOidc(); - - if (configuration.getOpenIdConnectAuthenticationProperties() != null) { - this.requestBuilder = - new OpenIdRequestBuilder( - application, configuration.getOpenIdConnectAuthenticationProperties()); - } else if (configuration.getOpenIdConnectAuthenticationPolicySecret() != null) { - throw new UnsupportedOperationException("Secrets are still not supported"); - } - } - - @Override - protected String authParameter(WorkflowContext workflow, TaskContext task, WorkflowModel model) { - return requestBuilder.build(workflow, task, model).validateAndGet().token(); - } - - @Override - protected String authScheme() { - return "Bearer"; + super( + accessToken( + workflow, + authPolicy.getOidc().getOpenIdConnectAuthenticationProperties(), + authPolicy.getOidc().getOpenIdConnectAuthenticationPolicySecret(), + new OpenIdRequestBuilder(application))); } } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/SecretKeys.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/SecretKeys.java new file mode 100644 index 000000000..8f34268ba --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/SecretKeys.java @@ -0,0 +1,37 @@ +/* + * 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; + +public class SecretKeys { + + private SecretKeys() {} + + public static final String GRANT = "grant"; + public static final String USER = "username"; + public static final String CLIENT = "client"; + public static final String PASSWORD = "password"; + public static final String ID = "id"; + public static final String SECRET = "secret"; + public static final String ISSUERS = "issuers"; + public static final String AUDIENCES = "audiences"; + public static final String ENDPOINTS = "endpoints"; + public static final String TOKEN = "token"; + public static final String AUTHORITY = "authority"; + public static final String SCOPES = "scopes"; + public static final String REQUEST = "request"; + public static final String ENCODING = "encoding"; + public static final String AUTHENTICATION = "authentication"; +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AbstractAuthRequestBuilder.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AbstractAuthRequestBuilder.java index 1925982fb..64031402e 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AbstractAuthRequestBuilder.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AbstractAuthRequestBuilder.java @@ -16,89 +16,121 @@ package io.serverlessworkflow.impl.executors.http.auth.requestbuilder; import static io.serverlessworkflow.api.types.OAuth2AuthenticationDataClient.ClientAuthentication.CLIENT_SECRET_POST; +import static io.serverlessworkflow.impl.WorkflowUtils.isValid; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.AUDIENCES; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.AUTHENTICATION; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.CLIENT; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.ENCODING; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.REQUEST; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.SCOPES; import io.serverlessworkflow.api.types.OAuth2AuthenticationData; import io.serverlessworkflow.api.types.OAuth2AuthenticationDataClient; -import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; -import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowUtils; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Objects; -import java.util.function.Consumer; import java.util.stream.Collectors; -abstract class AbstractAuthRequestBuilder implements AuthRequestBuilder { - - protected final OAuth2AuthenticationData authenticationData; +abstract class AbstractAuthRequestBuilder + implements AuthRequestBuilder { protected final WorkflowApplication application; + protected final HttpRequestInfoBuilder requestBuilder = new HttpRequestInfoBuilder(); - private final List> steps = - List.of( - this::requestEncoding, - this::authenticationURI, - this::audience, - this::scope, - this::authenticationMethod); - - protected AbstractAuthRequestBuilder( - OAuth2AuthenticationData authenticationData, WorkflowApplication application) { - this.authenticationData = authenticationData; + public AbstractAuthRequestBuilder(WorkflowApplication application) { this.application = application; } - protected void audience(HttpRequestBuilder requestBuilder) { + @Override + public HttpRequestInfo apply(T authenticationData) { + requestEncoding(authenticationData); + authenticationURI(authenticationData); + audience(authenticationData); + scope(authenticationData); + authenticationMethod(authenticationData); + return requestBuilder.build(); + } + + @Override + public HttpRequestInfo apply(Map secret) { + requestEncoding(secret); + authenticationURI(secret); + audience(secret); + scope(secret); + authenticationMethod(secret); + return requestBuilder.build(); + } + + protected void audience(T authenticationData) { if (authenticationData.getAudiences() != null && !authenticationData.getAudiences().isEmpty()) { String audiences = String.join(" ", authenticationData.getAudiences()); + requestBuilder.addQueryParam( + "audience", WorkflowUtils.buildStringFilter(application, audiences)); + } + } + + protected void audience(Map secret) { + String audiences = (String) secret.get(AUDIENCES); + if (isValid(audiences)) { requestBuilder.addQueryParam("audience", audiences); } } - protected void authenticationMethod(HttpRequestBuilder requestBuilder) { - switch (getClientAuthentication()) { + protected void authenticationMethod(T authenticationData) { + ClientSecretHandler secretHandler; + switch (getClientAuthentication(authenticationData)) { case CLIENT_SECRET_BASIC: - clientSecretBasic(requestBuilder); + 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: - clientSecretPost(requestBuilder); + secretHandler = new ClientSecretPost(application, requestBuilder); } + secretHandler.accept(authenticationData); } - private void clientSecretBasic(HttpRequestBuilder requestBuilder) { - new ClientSecretBasic(authenticationData).execute(requestBuilder); - } - - private void clientSecretPost(HttpRequestBuilder requestBuilder) { - new ClientSecretPost(authenticationData).execute(requestBuilder); - } - - private OAuth2AuthenticationDataClient.ClientAuthentication getClientAuthentication() { - if (authenticationData.getClient() == null - || authenticationData.getClient().getAuthentication() == null) { - return CLIENT_SECRET_POST; + protected void authenticationMethod(Map secret) { + Map client = (Map) secret.get(CLIENT); + ClientSecretHandler secretHandler; + String auth = (String) client.get(AUTHENTICATION); + 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"); + } } - return authenticationData.getClient().getAuthentication(); + secretHandler.accept(secret); } - @Override - public AccessTokenProvider build( - WorkflowContext workflow, TaskContext task, WorkflowModel model) { - HttpRequestBuilder requestBuilder = new HttpRequestBuilder(application); - steps.forEach(step -> step.accept(requestBuilder)); - return new AccessTokenProvider( - requestBuilder.build(workflow, task, model), task, authenticationData.getIssuers()); + private OAuth2AuthenticationDataClient.ClientAuthentication getClientAuthentication( + OAuth2AuthenticationData authenticationData) { + return authenticationData.getClient() == null + || authenticationData.getClient().getAuthentication() == null + ? CLIENT_SECRET_POST + : authenticationData.getClient().getAuthentication(); } - protected void scope(HttpRequestBuilder requestBuilder) { - scope(requestBuilder, authenticationData.getScopes()); + protected void scope(T authenticationData) { + scope(authenticationData.getScopes()); } - protected void scope(HttpRequestBuilder requestBuilder, List scopesList) { + protected void scope(List scopesList) { if (scopesList == null || scopesList.isEmpty()) { return; } @@ -112,19 +144,30 @@ protected void scope(HttpRequestBuilder requestBuilder, List scopesList) .collect(Collectors.joining(" ")); if (!scope.isEmpty()) { - requestBuilder.addQueryParam("scope", scope); + requestBuilder.addQueryParam("scope", WorkflowUtils.buildStringFilter(application, scope)); } } - void requestEncoding(HttpRequestBuilder requestBuilder) { - if (authenticationData.getRequest() != null - && authenticationData.getRequest().getEncoding() != null) { - requestBuilder.addHeader( - "Content-Type", authenticationData.getRequest().getEncoding().value()); - } else { - requestBuilder.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + protected void scope(Map secret) { + String scopes = (String) secret.get(SCOPES); + if (isValid(scopes)) { + requestBuilder.addQueryParam("scope", scopes); } } - protected abstract void authenticationURI(HttpRequestBuilder requestBuilder); + void requestEncoding(T authenticationData) { + requestBuilder.withContentType(authenticationData.getRequest()); + } + + void requestEncoding(Map secret) { + Map request = (Map) secret.get(REQUEST); + String encoding = (String) request.get(ENCODING); + if (isValid(encoding)) { + requestBuilder.addHeader("Content-Type", encoding); + } + } + + protected abstract void authenticationURI(T authenticationData); + + protected abstract void authenticationURI(Map secret); } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AccessTokenProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AccessTokenProvider.java index aea3dbd5f..7989f3a68 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AccessTokenProvider.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AccessTokenProvider.java @@ -15,38 +15,46 @@ */ package io.serverlessworkflow.impl.executors.http.auth.requestbuilder; +import static io.serverlessworkflow.api.types.OAuth2TokenRequest.Oauth2TokenRequestEncoding.APPLICATION_X_WWW_FORM_URLENCODED; + import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowError; +import io.serverlessworkflow.impl.WorkflowException; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.executors.http.HttpClientResolver; import io.serverlessworkflow.impl.executors.http.auth.jwt.JWT; import io.serverlessworkflow.impl.executors.http.auth.jwt.JWTConverter; +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.ServiceLoader; public class AccessTokenProvider { - private final TokenResponseHandler tokenResponseHandler = new TokenResponseHandler(); - - private final TaskContext context; private final List issuers; - private final InvocationHolder invocation; - + private final HttpRequestInfo requestInfo; private final JWTConverter jwtConverter; - AccessTokenProvider(InvocationHolder invocation, TaskContext context, List issuers) { - this.invocation = invocation; + AccessTokenProvider(HttpRequestInfo requestInfo, List issuers, JWTConverter converter) { + this.requestInfo = requestInfo; this.issuers = issuers; - this.context = context; - - this.jwtConverter = - ServiceLoader.load(JWTConverter.class) - .findFirst() - .orElseThrow(() -> new IllegalStateException("No JWTConverter implementation found")); + this.jwtConverter = converter; } - public JWT validateAndGet() { - Map token = tokenResponseHandler.apply(invocation, context); + public JWT validateAndGet(WorkflowContext workflow, TaskContext context, WorkflowModel model) { + Map token = invoke(workflow, context, model); JWT jwt = jwtConverter.fromToken((String) token.get("access_token")); - if (!(issuers == null || issuers.isEmpty())) { + if (issuers != null && !issuers.isEmpty()) { jwt.issuer() .ifPresent( issuer -> { @@ -57,4 +65,77 @@ public JWT validateAndGet() { } return jwt; } + + 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); + } + } + + private Response executeRequest(WorkflowContext workflow, TaskContext task, WorkflowModel model) { + + Client client = HttpClientResolver.client(workflow, task); + WebTarget target = client.target(requestInfo.uri().apply(workflow, task, model)); + + Invocation.Builder builder = target.request(MediaType.APPLICATION_JSON); + + 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())) { + Form form = new Form(); + form.param("grant_type", requestInfo.grantType()); + requestInfo + .queryParams() + .forEach((key, value) -> form.param(key, value.apply(workflow, task, model))); + 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, model))); + entity = Entity.entity(jsonData, MediaType.APPLICATION_JSON); + } + + return builder.post(entity); + } } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AccessTokenProviderFactory.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AccessTokenProviderFactory.java new file mode 100644 index 000000000..73d1defe9 --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AccessTokenProviderFactory.java @@ -0,0 +1,55 @@ +/* + * 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.requestbuilder; + +import static io.serverlessworkflow.impl.WorkflowUtils.secret; + +import io.serverlessworkflow.api.types.OAuth2AuthenticationData; +import io.serverlessworkflow.impl.WorkflowValueResolver; +import io.serverlessworkflow.impl.executors.http.auth.jwt.JWTConverter; +import java.util.Arrays; +import java.util.Map; +import java.util.ServiceLoader; + +public class AccessTokenProviderFactory { + + private AccessTokenProviderFactory() {} + + private static JWTConverter jwtConverter = + ServiceLoader.load(JWTConverter.class) + .findFirst() + .orElseThrow(() -> new IllegalStateException("No JWTConverter implementation found")); + + public static WorkflowValueResolver build( + OAuth2AuthenticationData authenticationData, AuthRequestBuilder authBuilder) { + AccessTokenProvider tokenProvider = + new AccessTokenProvider( + authBuilder.apply(authenticationData), authenticationData.getIssuers(), jwtConverter); + return (w, t, m) -> tokenProvider; + } + + public static WorkflowValueResolver build( + String secretName, AuthRequestBuilder authBuilder) { + return (w, t, m) -> { + Map secret = secret(w, secretName); + String issuers = (String) secret.get("issuers"); + return new AccessTokenProvider( + authBuilder.apply(secret), + issuers != null ? Arrays.asList(issuers.split(",")) : null, + jwtConverter); + }; + } +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AuthRequestBuilder.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AuthRequestBuilder.java index 6fc808d37..833c5ef9e 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AuthRequestBuilder.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/AuthRequestBuilder.java @@ -15,11 +15,12 @@ */ package io.serverlessworkflow.impl.executors.http.auth.requestbuilder; -import io.serverlessworkflow.impl.TaskContext; -import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.api.types.OAuth2AuthenticationData; +import java.util.Map; -public interface AuthRequestBuilder { +public interface AuthRequestBuilder { - public AccessTokenProvider build(WorkflowContext workflow, TaskContext task, WorkflowModel model); + HttpRequestInfo apply(T authenticationData); + + HttpRequestInfo apply(Map authenticationData); } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/ClientSecretBasic.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/ClientSecretBasic.java index 95b09abb1..542272242 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/ClientSecretBasic.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/ClientSecretBasic.java @@ -15,72 +15,71 @@ */ package io.serverlessworkflow.impl.executors.http.auth.requestbuilder; -import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS; -import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.PASSWORD; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.CLIENT; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.GRANT; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.ID; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.PASSWORD; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.SECRET; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.USER; import io.serverlessworkflow.api.types.OAuth2AuthenticationData; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowUtils; import java.util.Base64; +import java.util.Map; -class ClientSecretBasic { +class ClientSecretBasic extends ClientSecretHandler { - private final OAuth2AuthenticationData authenticationData; - - ClientSecretBasic(OAuth2AuthenticationData authenticationData) { - this.authenticationData = authenticationData; + protected ClientSecretBasic( + WorkflowApplication application, HttpRequestInfoBuilder requestBuilder) { + super(application, requestBuilder); } - void execute(HttpRequestBuilder requestBuilder) { - if (authenticationData.getGrant().equals(PASSWORD)) { - password(requestBuilder, authenticationData); - } else if (authenticationData.getGrant().equals(CLIENT_CREDENTIALS)) { - clientCredentials(requestBuilder, authenticationData); - } else { - throw new UnsupportedOperationException( - "Unsupported grant type: " + authenticationData.getGrant()); - } + @Override + protected void clientCredentials(OAuth2AuthenticationData authenticationData) { + requestBuilder + .addHeader("Authorization", "Basic " + encodedAuth(authenticationData)) + .withGrantType(authenticationData.getGrant().value()); } - private void clientCredentials( - HttpRequestBuilder requestBuilder, OAuth2AuthenticationData authenticationData) { - if (authenticationData.getClient() == null - || authenticationData.getClient().getId() == null - || authenticationData.getClient().getSecret() == null) { - throw new IllegalArgumentException( - "Client ID and secret must be provided for client authentication"); - } + @Override + protected void password(OAuth2AuthenticationData authenticationData) { + clientCredentials(authenticationData); + requestBuilder + .addQueryParam( + "username", + WorkflowUtils.buildStringFilter(application, authenticationData.getUsername())) + .addQueryParam( + "password", + WorkflowUtils.buildStringFilter(application, authenticationData.getPassword())); + } - String idAndSecret = - authenticationData.getClient().getId() + ":" + authenticationData.getClient().getSecret(); - String encodedAuth = Base64.getEncoder().encodeToString(idAndSecret.getBytes()); + @Override + protected void clientCredentials(Map secret) { + requestBuilder + .withGrantType((String) secret.get(GRANT)) + .addHeader("Authorization", "Basic " + encodedAuth(secret)); + } + @Override + protected void password(Map secret) { + clientCredentials(secret); requestBuilder - .addHeader("Authorization", "Basic " + encodedAuth) - .withRequestContentType(authenticationData.getRequest()) - .withGrantType(authenticationData.getGrant()); + .addQueryParam("username", (String) secret.get(USER)) + .addQueryParam("password", (String) secret.get(PASSWORD)); } - private void password( - HttpRequestBuilder requestBuilder, OAuth2AuthenticationData authenticationData) { - if (authenticationData.getUsername() == null || authenticationData.getPassword() == null) { - throw new IllegalArgumentException( - "Username and password must be provided for password grant type"); - } - if (authenticationData.getClient() == null - || authenticationData.getClient().getId() == null - || authenticationData.getClient().getSecret() == null) { - throw new IllegalArgumentException( - "Client ID and secret must be provided for client authentication"); - } + private String encodedAuth(Map secret) { + Map client = (Map) secret.get(CLIENT); + return encodedAuth((String) client.get(ID), (String) client.get(SECRET)); + } - String idAndSecret = - authenticationData.getClient().getId() + ":" + authenticationData.getClient().getSecret(); - String encodedAuth = Base64.getEncoder().encodeToString(idAndSecret.getBytes()); + private String encodedAuth(OAuth2AuthenticationData authenticationData) { + return encodedAuth( + authenticationData.getClient().getId(), authenticationData.getClient().getSecret()); + } - requestBuilder - .withGrantType(authenticationData.getGrant()) - .withRequestContentType(authenticationData.getRequest()) - .addHeader("Authorization", "Basic " + encodedAuth) - .addQueryParam("username", authenticationData.getUsername()) - .addQueryParam("password", authenticationData.getPassword()); + private String encodedAuth(String id, String secret) { + return Base64.getEncoder().encodeToString((id + ":" + secret).getBytes()); } } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/ClientSecretHandler.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/ClientSecretHandler.java new file mode 100644 index 000000000..4f8ae73a6 --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/ClientSecretHandler.java @@ -0,0 +1,86 @@ +/* + * 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.requestbuilder; + +import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS; +import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.PASSWORD; + +import io.serverlessworkflow.api.types.OAuth2AuthenticationData; +import io.serverlessworkflow.impl.WorkflowApplication; +import java.util.Map; +import java.util.Objects; + +abstract class ClientSecretHandler { + + protected final WorkflowApplication application; + protected final HttpRequestInfoBuilder requestBuilder; + + protected ClientSecretHandler( + WorkflowApplication application, HttpRequestInfoBuilder requestBuilder) { + this.application = application; + this.requestBuilder = requestBuilder; + } + + void accept(OAuth2AuthenticationData authenticationData) { + 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"); + } + if (authenticationData.getClient() == null + || authenticationData.getClient().getId() == null + || authenticationData.getClient().getSecret() == null) { + throw new IllegalArgumentException( + "Client ID and secret must be provided for client authentication"); + } + + password(authenticationData); + } else if (authenticationData.getGrant().equals(CLIENT_CREDENTIALS)) { + if (authenticationData.getClient() == null + || authenticationData.getClient().getId() == null + || authenticationData.getClient().getSecret() == null) { + throw new IllegalArgumentException( + "Client ID and secret must be provided for client authentication"); + } + clientCredentials(authenticationData); + } else { + throw new UnsupportedOperationException( + "Unsupported grant type: " + authenticationData.getGrant()); + } + } + + protected abstract void clientCredentials(OAuth2AuthenticationData authenticationData); + + protected abstract void password(OAuth2AuthenticationData authenticationData); + + protected abstract void clientCredentials(Map secret); + + protected abstract void password(Map secret); + + void accept(Map secret) { + String grant = Objects.requireNonNull((String) secret.get("grant"), "Grant is mandatory field"); + switch (grant) { + case "client_credentials": + clientCredentials(secret); + break; + case "password": + password(secret); + break; + default: + throw new UnsupportedOperationException("Unsupported grant type: " + grant); + } + } +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/ClientSecretPost.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/ClientSecretPost.java index 21302e30f..f0d3cbffd 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/ClientSecretPost.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/ClientSecretPost.java @@ -15,64 +15,63 @@ */ package io.serverlessworkflow.impl.executors.http.auth.requestbuilder; -import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS; -import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.PASSWORD; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.CLIENT; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.GRANT; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.ID; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.PASSWORD; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.SECRET; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.USER; import io.serverlessworkflow.api.types.OAuth2AuthenticationData; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowUtils; +import java.util.Map; -class ClientSecretPost { - private final OAuth2AuthenticationData authenticationData; +class ClientSecretPost extends ClientSecretHandler { - ClientSecretPost(OAuth2AuthenticationData authenticationData) { - this.authenticationData = authenticationData; + protected ClientSecretPost( + WorkflowApplication application, HttpRequestInfoBuilder requestBuilder) { + super(application, requestBuilder); } - void execute(HttpRequestBuilder requestBuilder) { - if (authenticationData.getGrant().equals(PASSWORD)) { - password(requestBuilder, authenticationData); - } else if (authenticationData.getGrant().equals(CLIENT_CREDENTIALS)) { - clientCredentials(requestBuilder, authenticationData); - } else { - throw new UnsupportedOperationException( - "Unsupported grant type: " + authenticationData.getGrant()); - } + @Override + protected void clientCredentials(OAuth2AuthenticationData authenticationData) { + requestBuilder + .withGrantType(authenticationData.getGrant().value()) + .addQueryParam( + "client_id", + WorkflowUtils.buildStringFilter(application, authenticationData.getClient().getId())) + .addQueryParam( + "client_secret", + WorkflowUtils.buildStringFilter( + application, authenticationData.getClient().getSecret())); } - private void clientCredentials( - HttpRequestBuilder requestBuilder, OAuth2AuthenticationData authenticationData) { - if (authenticationData.getClient() == null - || authenticationData.getClient().getId() == null - || authenticationData.getClient().getSecret() == null) { - throw new IllegalArgumentException( - "Client ID and secret must be provided for client authentication"); - } - + @Override + protected void password(OAuth2AuthenticationData authenticationData) { + clientCredentials(authenticationData); requestBuilder - .withGrantType(authenticationData.getGrant()) - .withRequestContentType(authenticationData.getRequest()) - .addQueryParam("client_id", authenticationData.getClient().getId()) - .addQueryParam("client_secret", authenticationData.getClient().getSecret()); + .addQueryParam( + "username", + WorkflowUtils.buildStringFilter(application, authenticationData.getUsername())) + .addQueryParam( + "password", + WorkflowUtils.buildStringFilter(application, authenticationData.getPassword())); } - private void password( - HttpRequestBuilder requestBuilder, OAuth2AuthenticationData authenticationData) { - if (authenticationData.getUsername() == null || authenticationData.getPassword() == null) { - throw new IllegalArgumentException( - "Username and password must be provided for password grant type"); - } - if (authenticationData.getClient() == null - || authenticationData.getClient().getId() == null - || authenticationData.getClient().getSecret() == null) { - throw new IllegalArgumentException( - "Client ID and secret must be provided for client authentication"); - } - + @Override + protected void clientCredentials(Map secret) { + Map client = (Map) secret.get(CLIENT); requestBuilder - .withGrantType(authenticationData.getGrant()) - .withRequestContentType(authenticationData.getRequest()) - .addQueryParam("client_id", authenticationData.getClient().getId()) - .addQueryParam("client_secret", authenticationData.getClient().getSecret()) - .addQueryParam("username", authenticationData.getUsername()) - .addQueryParam("password", authenticationData.getPassword()); + .withGrantType((String) secret.get(GRANT)) + .addQueryParam("client_id", (String) client.get(ID)) + .addQueryParam("client_secret", (String) client.get(SECRET)); + } + + @Override + protected void password(Map secret) { + clientCredentials(secret); + requestBuilder.addQueryParam("username", (String) secret.get(USER)); + requestBuilder.addQueryParam("password", (String) secret.get(PASSWORD)); } } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/HttpRequestBuilder.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/HttpRequestBuilder.java deleted file mode 100644 index 14f90421d..000000000 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/HttpRequestBuilder.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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.requestbuilder; - -import static io.serverlessworkflow.api.types.OAuth2TokenRequest.Oauth2TokenRequestEncoding.APPLICATION_X_WWW_FORM_URLENCODED; - -import io.serverlessworkflow.api.types.OAuth2AuthenticationData; -import io.serverlessworkflow.api.types.OAuth2TokenRequest; -import io.serverlessworkflow.api.types.OAuth2TokenRequest.Oauth2TokenRequestEncoding; -import io.serverlessworkflow.impl.TaskContext; -import io.serverlessworkflow.impl.WorkflowApplication; -import io.serverlessworkflow.impl.WorkflowContext; -import io.serverlessworkflow.impl.WorkflowModel; -import io.serverlessworkflow.impl.WorkflowUtils; -import io.serverlessworkflow.impl.WorkflowValueResolver; -import jakarta.ws.rs.client.Client; -import jakarta.ws.rs.client.ClientBuilder; -import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.client.Invocation; -import jakarta.ws.rs.client.WebTarget; -import jakarta.ws.rs.core.Form; -import jakarta.ws.rs.core.MediaType; -import java.net.URI; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -class HttpRequestBuilder { - - private final Map> headers; - - private final Map> queryParams; - - private final WorkflowApplication app; - - private URI uri; - - private OAuth2AuthenticationData.OAuth2AuthenticationDataGrant grantType; - - private Oauth2TokenRequestEncoding requestContentType = APPLICATION_X_WWW_FORM_URLENCODED; - - HttpRequestBuilder(WorkflowApplication app) { - this.app = app; - headers = new HashMap<>(); - queryParams = new HashMap<>(); - } - - HttpRequestBuilder addHeader(String key, String token) { - headers.put(key, WorkflowUtils.buildStringFilter(app, token)); - return this; - } - - HttpRequestBuilder addQueryParam(String key, String token) { - queryParams.put(key, WorkflowUtils.buildStringFilter(app, token)); - return this; - } - - HttpRequestBuilder withUri(URI uri) { - this.uri = uri; - return this; - } - - HttpRequestBuilder withRequestContentType(OAuth2TokenRequest oAuth2TokenRequest) { - if (oAuth2TokenRequest != null) { - this.requestContentType = oAuth2TokenRequest.getEncoding(); - } - return this; - } - - HttpRequestBuilder withGrantType( - OAuth2AuthenticationData.OAuth2AuthenticationDataGrant grantType) { - this.grantType = grantType; - return this; - } - - InvocationHolder build(WorkflowContext workflow, TaskContext task, WorkflowModel model) { - validate(); - - Client client = ClientBuilder.newClient(); - WebTarget target = client.target(uri); - - Invocation.Builder builder = target.request(MediaType.APPLICATION_JSON); - - builder.header("grant_type", grantType.name().toLowerCase()); - builder.header("User-Agent", "OAuth2-Client-Credentials/1.0"); - builder.header("Accept", MediaType.APPLICATION_JSON); - builder.header("Cache-Control", "no-cache"); - - for (var entry : headers.entrySet()) { - String headerValue = entry.getValue().apply(workflow, task, model); - if (headerValue != null) { - builder.header(entry.getKey(), headerValue); - } - } - - Entity entity; - if (requestContentType.equals(APPLICATION_X_WWW_FORM_URLENCODED)) { - Form form = new Form(); - form.param("grant_type", grantType.value()); - queryParams.forEach( - (key, value) -> { - String resolved = value.apply(workflow, task, model); - form.param(key, resolved); - }); - entity = Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED); - } else { - Map jsonData = new HashMap<>(); - jsonData.put("grant_type", grantType.value()); - queryParams.forEach( - (key, value) -> { - String resolved = value.apply(workflow, task, model); - jsonData.put(key, resolved); - }); - entity = Entity.entity(jsonData, MediaType.APPLICATION_JSON); - } - - return new InvocationHolder(client, () -> builder.post(entity)); - } - - private void validate() { - Objects.requireNonNull(uri, "URI must be set before building the request"); - Objects.requireNonNull(grantType, "Grant type must be set before building the request"); - } -} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/InvocationHolder.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/HttpRequestInfo.java similarity index 55% rename from impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/InvocationHolder.java rename to impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/HttpRequestInfo.java index e1cca56e5..b2ef5c3fa 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/InvocationHolder.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/HttpRequestInfo.java @@ -15,29 +15,13 @@ */ package io.serverlessworkflow.impl.executors.http.auth.requestbuilder; -import jakarta.ws.rs.client.Client; -import jakarta.ws.rs.core.Response; -import java.io.Closeable; -import java.util.concurrent.Callable; -import java.util.function.Supplier; +import io.serverlessworkflow.impl.WorkflowValueResolver; +import java.net.URI; +import java.util.Map; -class InvocationHolder implements Callable, Closeable { - - private final Client client; - private final Supplier call; - - InvocationHolder(Client client, Supplier call) { - this.client = client; - this.call = call; - } - - public Response call() { - return call.get(); - } - - public void close() { - if (client != null) { - client.close(); - } - } -} +record HttpRequestInfo( + Map> headers, + Map> queryParams, + WorkflowValueResolver uri, + String grantType, + String contentType) {} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/HttpRequestInfoBuilder.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/HttpRequestInfoBuilder.java new file mode 100644 index 000000000..a9d9f0bdb --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/HttpRequestInfoBuilder.java @@ -0,0 +1,96 @@ +/* + * 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.requestbuilder; + +import static io.serverlessworkflow.api.types.OAuth2TokenRequest.Oauth2TokenRequestEncoding.APPLICATION_X_WWW_FORM_URLENCODED; + +import io.serverlessworkflow.api.types.OAuth2TokenRequest; +import io.serverlessworkflow.impl.WorkflowValueResolver; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +class HttpRequestInfoBuilder { + + private Map> headers; + + private Map> queryParams; + + private WorkflowValueResolver uri; + + private String grantType; + + private String contentType; + + HttpRequestInfoBuilder() { + headers = new HashMap<>(); + queryParams = new HashMap<>(); + } + + HttpRequestInfoBuilder addHeader(String key, String token) { + headers.put(key, (w, t, m) -> token); + return this; + } + + HttpRequestInfoBuilder addHeader(String key, WorkflowValueResolver token) { + headers.put(key, token); + return this; + } + + HttpRequestInfoBuilder addQueryParam(String key, String token) { + queryParams.put(key, (w, t, m) -> token); + return this; + } + + HttpRequestInfoBuilder addQueryParam(String key, WorkflowValueResolver token) { + queryParams.put(key, token); + return this; + } + + HttpRequestInfoBuilder withUri(WorkflowValueResolver uri) { + this.uri = uri; + return this; + } + + HttpRequestInfoBuilder withContentType(OAuth2TokenRequest oAuth2TokenRequest) { + if (oAuth2TokenRequest != null) { + this.contentType = oAuth2TokenRequest.getEncoding().value(); + } + return this; + } + + HttpRequestInfoBuilder withContentType(String contentType) { + if (contentType != null) { + this.contentType = contentType; + } + return this; + } + + HttpRequestInfoBuilder withGrantType(String grantType) { + this.grantType = grantType; + return this; + } + + HttpRequestInfo 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(); + } + return new HttpRequestInfo(headers, queryParams, uri, grantType, contentType); + } +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/OAuthRequestBuilder.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/OAuthRequestBuilder.java index 6ef16fcdc..df93b95d9 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/OAuthRequestBuilder.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/OAuthRequestBuilder.java @@ -15,37 +15,50 @@ */ package io.serverlessworkflow.impl.executors.http.auth.requestbuilder; +import static io.serverlessworkflow.impl.WorkflowUtils.concatURI; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.AUTHORITY; + import io.serverlessworkflow.api.types.OAuth2AuthenticationPropertiesEndpoints; import io.serverlessworkflow.api.types.OAuth2ConnectAuthenticationProperties; import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.WorkflowValueResolver; import java.net.URI; import java.util.Map; -public class OAuthRequestBuilder extends AbstractAuthRequestBuilder { +public class OAuthRequestBuilder + extends AbstractAuthRequestBuilder { + + private static String DEFAULT_TOKEN_PATH = "oauth2/token"; - private final Map defaults = - Map.of( - "endpoints.token", "oauth2/token", - "endpoints.revocation", "oauth2/revoke", - "endpoints.introspection", "oauth2/introspect"); + public OAuthRequestBuilder(WorkflowApplication application) { + super(application); + } - public OAuthRequestBuilder( - WorkflowApplication application, - OAuth2ConnectAuthenticationProperties oAuth2ConnectAuthenticationProperties) { - super(oAuth2ConnectAuthenticationProperties, 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)); } @Override - protected void authenticationURI(HttpRequestBuilder requestBuilder) { - OAuth2AuthenticationPropertiesEndpoints endpoints = - ((OAuth2ConnectAuthenticationProperties) authenticationData).getEndpoints(); - - String baseUri = - authenticationData.getAuthority().getLiteralUri().toString().replaceAll("/$", ""); - String tokenPath = defaults.get("endpoints.token"); - if (endpoints != null && endpoints.getToken() != null) { - tokenPath = endpoints.getToken().replaceAll("^/", ""); - } - requestBuilder.withUri(URI.create(baseUri + "/" + tokenPath)); + 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); } } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/OpenIdRequestBuilder.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/OpenIdRequestBuilder.java index eb082f59d..6059228ab 100644 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/OpenIdRequestBuilder.java +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/OpenIdRequestBuilder.java @@ -15,29 +15,38 @@ */ package io.serverlessworkflow.impl.executors.http.auth.requestbuilder; +import static io.serverlessworkflow.impl.executors.http.SecretKeys.AUTHORITY; + import io.serverlessworkflow.api.types.OAuth2AuthenticationData; import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowUtils; import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.Map; -public class OpenIdRequestBuilder extends AbstractAuthRequestBuilder { +public class OpenIdRequestBuilder extends AbstractAuthRequestBuilder { - public OpenIdRequestBuilder( - WorkflowApplication application, OAuth2AuthenticationData autenthicationData) { - super(autenthicationData, application); + public OpenIdRequestBuilder(WorkflowApplication application) { + super(application); } @Override - protected void authenticationURI(HttpRequestBuilder requestBuilder) { - String url = authenticationData.getAuthority().getLiteralUri().toString().replaceAll("/$", ""); - requestBuilder.withUri(URI.create(url)); + protected void authenticationURI(OAuth2AuthenticationData authenticationData) { + requestBuilder.withUri( + WorkflowUtils.getURISupplier(application, authenticationData.getAuthority())); } @Override - protected void scope(HttpRequestBuilder requestBuilder) { + protected void scope(OAuth2AuthenticationData authenticationData) { List scopesList = new ArrayList<>(authenticationData.getScopes()); scopesList.add("openid"); - scope(requestBuilder, scopesList); + scope(scopesList); + } + + @Override + protected void authenticationURI(Map secret) { + URI uri = URI.create((String) secret.get(AUTHORITY)); + requestBuilder.withUri((w, t, m) -> uri); } } diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/TokenResponseHandler.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/TokenResponseHandler.java deleted file mode 100644 index 2d64b7ecc..000000000 --- a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/requestbuilder/TokenResponseHandler.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.requestbuilder; - -import io.serverlessworkflow.impl.TaskContext; -import io.serverlessworkflow.impl.WorkflowError; -import io.serverlessworkflow.impl.WorkflowException; -import jakarta.ws.rs.ProcessingException; -import jakarta.ws.rs.client.ResponseProcessingException; -import jakarta.ws.rs.core.GenericType; -import jakarta.ws.rs.core.Response; -import java.util.Map; -import java.util.function.BiFunction; - -class TokenResponseHandler - implements BiFunction> { - - @Override - public Map apply(InvocationHolder invocation, TaskContext context) { - try (Response response = invocation.call()) { - if (response.getStatus() < 200 || response.getStatus() >= 300) { - throw new WorkflowException( - WorkflowError.communication( - response.getStatus(), - context, - "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(), - context, - "Failed to process response: " + e.getMessage()) - .build(), - e); - } catch (ProcessingException e) { - throw new WorkflowException( - WorkflowError.communication( - -1, context, "Failed to connect or process request: " + e.getMessage()) - .build(), - e); - } finally { - invocation.close(); - } - } -} diff --git a/impl/jq/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpression.java b/impl/jq/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpression.java index 8f95ed066..21edca3a0 100644 --- a/impl/jq/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpression.java +++ b/impl/jq/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpression.java @@ -108,7 +108,7 @@ private Scope createScope(WorkflowContext workflow, TaskContext task) { "secret", new FunctionJsonNode( k -> { - Map secret = + Map secret = workflow.definition().application().secretManager().secret(k); if (secret.isEmpty()) { throw new WorkflowException(WorkflowError.authorization().build()); diff --git a/impl/test/src/test/resources/workflows-samples/oAuthClientSecretPostPasswordAllGrantsHttpCall.yaml b/impl/test/src/test/resources/workflows-samples/oAuthClientSecretPostPasswordAllGrantsHttpCall.yaml index 804d8932a..78ee8fef1 100644 --- a/impl/test/src/test/resources/workflows-samples/oAuthClientSecretPostPasswordAllGrantsHttpCall.yaml +++ b/impl/test/src/test/resources/workflows-samples/oAuthClientSecretPostPasswordAllGrantsHttpCall.yaml @@ -12,7 +12,7 @@ do: uri: http://localhost:8081/hello authentication: oauth2: - authority: http://localhost:8888/realms/test-realm + authority: http://localhost:8888/realms/test-realm/ grant: password request: encoding: application/x-www-form-urlencoded