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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ public static Builder error(String type, int status) {
}

public static Builder communication(int status, TaskContext context, Exception ex) {
return new Builder(COMM_TYPE, status)
.instance(context.position().jsonPointer())
.title(ex.getMessage());
return communication(status, context, ex.getMessage());
}

public static Builder communication(int status, TaskContext context, String title) {
return new Builder(COMM_TYPE, status).instance(context.position().jsonPointer()).title(title);
}

public static Builder runtime(int status, TaskContext context, Exception ex) {
Expand Down
14 changes: 14 additions & 0 deletions impl/http/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
<groupId>io.serverlessworkflow</groupId>
<artifactId>serverlessworkflow-impl-core</artifactId>
</dependency>
<dependency>
<groupId>io.serverlessworkflow</groupId>
<artifactId>serverlessworkflow-jwt</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
Expand All @@ -34,6 +38,11 @@
<artifactId>serverlessworkflow-impl-jackson</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.serverlessworkflow</groupId>
<artifactId>serverlessworkflow-impl-jackson-jwt</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand All @@ -55,5 +64,10 @@
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,17 @@
import io.serverlessworkflow.impl.WorkflowModel;
import jakarta.ws.rs.client.Invocation;

@FunctionalInterface
interface AuthProvider {

default void preRequest(
Invocation.Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model) {
// Default implementation does nothing
}

default void postRequest(WorkflowContext workflow, TaskContext task, WorkflowModel model) {
// Default implementation does nothing
}

Invocation.Builder build(
Invocation.Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model);
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,10 @@ public CompletableFuture<WorkflowModel> apply(
return CompletableFuture.supplyAsync(
() -> {
try {
return requestFunction.apply(request, workflow, taskContext, input);
authProvider.ifPresent(auth -> auth.preRequest(request, workflow, taskContext, input));
WorkflowModel result = requestFunction.apply(request, workflow, taskContext, input);
authProvider.ifPresent(auth -> auth.postRequest(workflow, taskContext, input));
return result;
} catch (WebApplicationException exception) {
throw new WorkflowException(
WorkflowError.communication(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,46 @@
package io.serverlessworkflow.impl.executors.http;

import io.serverlessworkflow.api.types.OAuth2AuthenticationPolicy;
import io.serverlessworkflow.api.types.Oauth2;
import io.serverlessworkflow.api.types.Workflow;
import io.serverlessworkflow.http.jwt.JWT;
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.oauth.OAuthRequestBuilder;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.client.Invocation.Builder;

public class OAuth2AuthProvider implements AuthProvider {

private OAuthRequestBuilder requestBuilder;

private static final String BEARER_TOKEN = "%s %s";

public OAuth2AuthProvider(
WorkflowApplication app, Workflow workflow, OAuth2AuthenticationPolicy authPolicy) {
throw new UnsupportedOperationException("Oauth2 auth not supported yet");
WorkflowApplication application, Workflow workflow, OAuth2AuthenticationPolicy authPolicy) {
Oauth2 oauth2 = authPolicy.getOauth2();
if (oauth2.getOAuth2ConnectAuthenticationProperties() != null) {
this.requestBuilder = new OAuthRequestBuilder(application, oauth2);
} else if (oauth2.getOAuth2AuthenticationPolicySecret() != null) {
throw new UnsupportedOperationException("Secrets are still not supported");
}
}

@Override
public Builder build(
Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model) {
// TODO Auto-generated method stub
return builder;
}

@Override
public void preRequest(
Invocation.Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model) {
JWT token = requestBuilder.build(workflow, task, model).validateAndGet();
String tokenType = (String) token.getClaim("typ");
builder.header(
AuthProviderFactory.AUTH_HEADER_NAME,
String.format(BEARER_TOKEN, tokenType, token.getToken()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.oauth;

import io.serverlessworkflow.http.jwt.JWT;
import io.serverlessworkflow.http.jwt.JWTConverter;
import io.serverlessworkflow.impl.TaskContext;
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<String> issuers;
private final InvocationHolder invocation;

private final JWTConverter jwtConverter;

AccessTokenProvider(InvocationHolder invocation, TaskContext context, List<String> issuers) {
this.invocation = invocation;
this.issuers = issuers;
this.context = context;

this.jwtConverter =
ServiceLoader.load(JWTConverter.class)
.findFirst()
.orElseThrow(() -> new IllegalStateException("No JWTConverter implementation found"));
}

public JWT validateAndGet() {
Map<String, Object> token = tokenResponseHandler.apply(invocation, context);
JWT jwt = jwtConverter.fromToken((String) token.get("access_token"));
if (!(issuers == null || issuers.isEmpty())) {
String tokenIssuer = (String) jwt.getClaim("iss");
if (tokenIssuer == null || tokenIssuer.isEmpty() || !issuers.contains(tokenIssuer)) {
throw new IllegalStateException("Token issuer is not valid: " + tokenIssuer);
}
}
return jwt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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.oauth;

import static io.serverlessworkflow.api.types.OAuth2AutenthicationData.OAuth2AutenthicationDataGrant.CLIENT_CREDENTIALS;
import static io.serverlessworkflow.api.types.OAuth2AutenthicationData.OAuth2AutenthicationDataGrant.PASSWORD;

import io.serverlessworkflow.api.types.OAuth2AutenthicationData;
import io.serverlessworkflow.api.types.Oauth2;
import java.util.Base64;

class ClientSecretBasic {

private final Oauth2 oauth2;

public ClientSecretBasic(Oauth2 oauth2) {
this.oauth2 = oauth2;
}

public void execute(HttpRequestBuilder requestBuilder) {
OAuth2AutenthicationData authenticationData =
oauth2.getOAuth2ConnectAuthenticationProperties().getOAuth2AutenthicationData();
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());
}
}

private void clientCredentials(
HttpRequestBuilder requestBuilder, OAuth2AutenthicationData 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");
}

String idAndSecret =
authenticationData.getClient().getId() + ":" + authenticationData.getClient().getSecret();
String encodedAuth = Base64.getEncoder().encodeToString(idAndSecret.getBytes());

requestBuilder
.addHeader("Authorization", "Basic " + encodedAuth)
.withRequestContentType(authenticationData.getRequest())
.withGrantType(authenticationData.getGrant());
}

private void password(
HttpRequestBuilder requestBuilder, OAuth2AutenthicationData 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");
}

String idAndSecret =
authenticationData.getClient().getId() + ":" + authenticationData.getClient().getSecret();
String encodedAuth = Base64.getEncoder().encodeToString(idAndSecret.getBytes());

requestBuilder
.withGrantType(authenticationData.getGrant())
.withRequestContentType(authenticationData.getRequest())
.addHeader("Authorization", "Basic " + encodedAuth)
.addQueryParam("username", authenticationData.getUsername())
.addQueryParam("password", authenticationData.getPassword());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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.oauth;

import static io.serverlessworkflow.api.types.OAuth2AutenthicationData.OAuth2AutenthicationDataGrant.CLIENT_CREDENTIALS;
import static io.serverlessworkflow.api.types.OAuth2AutenthicationData.OAuth2AutenthicationDataGrant.PASSWORD;

import io.serverlessworkflow.api.types.OAuth2AutenthicationData;
import io.serverlessworkflow.api.types.Oauth2;

class ClientSecretPostStep {
private final Oauth2 oauth2;

public ClientSecretPostStep(Oauth2 oauth2) {
this.oauth2 = oauth2;
}

public void execute(HttpRequestBuilder requestBuilder) {
OAuth2AutenthicationData authenticationData =
oauth2.getOAuth2ConnectAuthenticationProperties().getOAuth2AutenthicationData();

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());
}
}

private void clientCredentials(
HttpRequestBuilder requestBuilder, OAuth2AutenthicationData 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");
}

requestBuilder
.withGrantType(authenticationData.getGrant())
.withRequestContentType(authenticationData.getRequest())
.addQueryParam("client_id", authenticationData.getClient().getId())
.addQueryParam("client_secret", authenticationData.getClient().getSecret());
}

private void password(
HttpRequestBuilder requestBuilder, OAuth2AutenthicationData 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");
}

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());
}
}
Loading