Skip to content

Commit 32ad156

Browse files
committed
HttpCallTask implementation lack OAuth authentication support
1 parent 499ce94 commit 32ad156

File tree

14 files changed

+722
-8
lines changed

14 files changed

+722
-8
lines changed

api/src/test/java/io/serverlessworkflow/api/ApiTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,23 @@ void testCallHTTPAPI() throws IOException {
6363
}
6464
}
6565

66+
@Test
67+
void testCallHttpOauthAPI() throws IOException {
68+
Workflow workflow = readWorkflowFromClasspath("features/authentication-oauth2.yaml");
69+
assertThat(workflow.getDo()).isNotEmpty();
70+
assertThat(workflow.getDo().get(0).getName()).isNotNull();
71+
assertThat(workflow.getDo().get(0).getTask()).isNotNull();
72+
Task task = workflow.getDo().get(0).getTask();
73+
if (task.get() instanceof CallTask) {
74+
CallTask callTask = task.getCallTask();
75+
assertThat(callTask).isNotNull();
76+
assertThat(task.getDoTask()).isNull();
77+
CallHTTP httpCall = callTask.getCallHTTP();
78+
assertThat(httpCall).isNotNull();
79+
assertThat(httpCall.getWith().getMethod()).isEqualTo("get");
80+
}
81+
}
82+
6683
@Test
6784
void testCallFunctionAPIWithoutArguments() throws IOException {
6885
Workflow workflow = readWorkflowFromClasspath("features/callFunction.yaml");

impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowError.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ public static Builder error(String type, int status) {
2727
}
2828

2929
public static Builder communication(int status, TaskContext context, Exception ex) {
30-
return new Builder(COMM_TYPE, status)
31-
.instance(context.position().jsonPointer())
32-
.title(ex.getMessage());
30+
return communication(status, context, ex.getMessage());
31+
}
32+
33+
public static Builder communication(int status, TaskContext context, String title) {
34+
return new Builder(COMM_TYPE, status).instance(context.position().jsonPointer()).title(title);
3335
}
3436

3537
public static Builder runtime(int status, TaskContext context, Exception ex) {

impl/http/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
<groupId>org.glassfish.jersey.core</groupId>
2525
<artifactId>jersey-client</artifactId>
2626
</dependency>
27+
<dependency>
28+
<groupId>com.fasterxml.jackson.core</groupId>
29+
<artifactId>jackson-databind</artifactId>
30+
</dependency>
2731
<dependency>
2832
<groupId>io.serverlessworkflow</groupId>
2933
<artifactId>serverlessworkflow-api</artifactId>

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AuthProvider.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,17 @@
2020
import io.serverlessworkflow.impl.WorkflowModel;
2121
import jakarta.ws.rs.client.Invocation;
2222

23-
@FunctionalInterface
2423
interface AuthProvider {
24+
25+
default void preRequest(
26+
Invocation.Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model) {
27+
// Default implementation does nothing
28+
}
29+
30+
default void postRequest(WorkflowContext workflow, TaskContext task, WorkflowModel model) {
31+
// Default implementation does nothing
32+
}
33+
2534
Invocation.Builder build(
2635
Invocation.Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model);
2736
}

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,10 @@ public CompletableFuture<WorkflowModel> apply(
154154
return CompletableFuture.supplyAsync(
155155
() -> {
156156
try {
157-
return requestFunction.apply(request, workflow, taskContext, input);
157+
authProvider.ifPresent(auth -> auth.preRequest(request, workflow, taskContext, input));
158+
WorkflowModel result = requestFunction.apply(request, workflow, taskContext, input);
159+
authProvider.ifPresent(auth -> auth.postRequest(workflow, taskContext, input));
160+
return result;
158161
} catch (WebApplicationException exception) {
159162
throw new WorkflowException(
160163
WorkflowError.communication(

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OAuth2AuthProvider.java

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,55 @@
1616
package io.serverlessworkflow.impl.executors.http;
1717

1818
import io.serverlessworkflow.api.types.OAuth2AuthenticationPolicy;
19+
import io.serverlessworkflow.api.types.Oauth2;
1920
import io.serverlessworkflow.api.types.Workflow;
2021
import io.serverlessworkflow.impl.TaskContext;
2122
import io.serverlessworkflow.impl.WorkflowApplication;
2223
import io.serverlessworkflow.impl.WorkflowContext;
2324
import io.serverlessworkflow.impl.WorkflowModel;
25+
import io.serverlessworkflow.impl.executors.http.oauth.JWT;
26+
import io.serverlessworkflow.impl.executors.http.oauth.OAuthRequestBuilder;
27+
import jakarta.ws.rs.client.Invocation;
2428
import jakarta.ws.rs.client.Invocation.Builder;
2529

2630
public class OAuth2AuthProvider implements AuthProvider {
2731

32+
private Oauth2 oauth2;
33+
34+
private WorkflowApplication workflowApplication;
35+
36+
private static final String BEARER_TOKEN = "%s %s";
37+
2838
public OAuth2AuthProvider(
29-
WorkflowApplication app, Workflow workflow, OAuth2AuthenticationPolicy authPolicy) {
30-
throw new UnsupportedOperationException("Oauth2 auth not supported yet");
39+
WorkflowApplication application, Workflow workflow, OAuth2AuthenticationPolicy authPolicy) {
40+
this.workflowApplication = application;
41+
Oauth2 oauth2 = authPolicy.getOauth2();
42+
43+
if (oauth2.getOAuth2ConnectAuthenticationProperties() != null) {
44+
this.oauth2 = oauth2;
45+
} else if (oauth2.getOAuth2AuthenticationPolicySecret() != null) {
46+
throw new UnsupportedOperationException("Secrets are still not supported");
47+
}
3148
}
3249

3350
@Override
3451
public Builder build(
3552
Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model) {
36-
// TODO Auto-generated method stub
3753
return builder;
3854
}
55+
56+
@Override
57+
public void preRequest(
58+
Invocation.Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model) {
59+
JWT token =
60+
new OAuthRequestBuilder(workflowApplication, oauth2)
61+
.build(workflow, task, model)
62+
.validateAndGet();
63+
64+
String tokenType = (String) token.getClaim("typ");
65+
66+
builder.header(
67+
AuthProviderFactory.AUTH_HEADER_NAME,
68+
String.format(BEARER_TOKEN, tokenType, token.getToken()));
69+
}
3970
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.serverlessworkflow.impl.executors.http.oauth;
18+
19+
import com.fasterxml.jackson.core.JsonProcessingException;
20+
import com.fasterxml.jackson.databind.JsonNode;
21+
import io.serverlessworkflow.impl.TaskContext;
22+
import java.net.http.HttpRequest;
23+
import java.util.List;
24+
25+
public class AccessTokenProvider {
26+
27+
private final TokenResponseHandler tokenResponseHandler = new TokenResponseHandler();
28+
29+
private final TaskContext context;
30+
private final List<String> issuers;
31+
private final HttpRequest requestBuilder;
32+
33+
public AccessTokenProvider(
34+
HttpRequest requestBuilder, TaskContext context, List<String> issuers) {
35+
this.requestBuilder = requestBuilder;
36+
this.issuers = issuers;
37+
this.context = context;
38+
}
39+
40+
public JWT validateAndGet() {
41+
JsonNode token = tokenResponseHandler.apply(requestBuilder, context);
42+
JWT jwt;
43+
try {
44+
jwt = JWT.fromString(token.get("access_token").asText());
45+
} catch (JsonProcessingException e) {
46+
throw new RuntimeException("Failed to parse JWT token: " + e.getMessage(), e);
47+
}
48+
if (!(issuers == null || issuers.isEmpty())) {
49+
String tokenIssuer = (String) jwt.getClaim("iss");
50+
if (tokenIssuer == null || tokenIssuer.isEmpty() || !issuers.contains(tokenIssuer)) {
51+
throw new RuntimeException("Token issuer is not valid: " + tokenIssuer);
52+
}
53+
}
54+
return jwt;
55+
}
56+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.serverlessworkflow.impl.executors.http.oauth;
18+
19+
import static io.serverlessworkflow.api.types.OAuth2AutenthicationData.OAuth2AutenthicationDataGrant.CLIENT_CREDENTIALS;
20+
import static io.serverlessworkflow.api.types.OAuth2AutenthicationData.OAuth2AutenthicationDataGrant.PASSWORD;
21+
22+
import io.serverlessworkflow.api.types.OAuth2AutenthicationData;
23+
import io.serverlessworkflow.api.types.Oauth2;
24+
import java.util.Base64;
25+
26+
class ClientSecretBasic {
27+
28+
private final Oauth2 oauth2;
29+
30+
public ClientSecretBasic(Oauth2 oauth2) {
31+
this.oauth2 = oauth2;
32+
}
33+
34+
public void execute(HttpRequestBuilder requestBuilder) {
35+
OAuth2AutenthicationData authenticationData =
36+
oauth2.getOAuth2ConnectAuthenticationProperties().getOAuth2AutenthicationData();
37+
38+
if (authenticationData.getGrant().equals(PASSWORD)) {
39+
password(requestBuilder);
40+
} else if (authenticationData.getGrant().equals(CLIENT_CREDENTIALS)) {
41+
clientCredentials(requestBuilder);
42+
} else {
43+
throw new UnsupportedOperationException(
44+
"Unsupported grant type: " + authenticationData.getGrant());
45+
}
46+
}
47+
48+
private void clientCredentials(HttpRequestBuilder requestBuilder) {
49+
OAuth2AutenthicationData authenticationData =
50+
oauth2.getOAuth2ConnectAuthenticationProperties().getOAuth2AutenthicationData();
51+
if (authenticationData.getClient() == null
52+
|| authenticationData.getClient().getId() == null
53+
|| authenticationData.getClient().getSecret() == null) {
54+
throw new IllegalArgumentException(
55+
"Client ID and secret must be provided for client authentication");
56+
}
57+
58+
String idAndSecret =
59+
authenticationData.getClient().getId() + ":" + authenticationData.getClient().getSecret();
60+
String encodedAuth = Base64.getEncoder().encodeToString(idAndSecret.getBytes());
61+
62+
requestBuilder
63+
.addHeader("Authorization", "Basic " + encodedAuth)
64+
.withMethod("POST")
65+
.addQueryParam("grant_type", "client_credentials");
66+
}
67+
68+
private void password(HttpRequestBuilder requestBuilder) {
69+
OAuth2AutenthicationData authenticationData =
70+
oauth2.getOAuth2ConnectAuthenticationProperties().getOAuth2AutenthicationData();
71+
if (authenticationData.getUsername() == null || authenticationData.getPassword() == null) {
72+
throw new IllegalArgumentException(
73+
"Username and password must be provided for password grant type");
74+
}
75+
if (authenticationData.getClient() == null
76+
|| authenticationData.getClient().getId() == null
77+
|| authenticationData.getClient().getSecret() == null) {
78+
throw new IllegalArgumentException(
79+
"Client ID and secret must be provided for client authentication");
80+
}
81+
82+
String idAndSecret =
83+
authenticationData.getClient().getId() + ":" + authenticationData.getClient().getSecret();
84+
String encodedAuth = Base64.getEncoder().encodeToString(idAndSecret.getBytes());
85+
86+
requestBuilder
87+
.withMethod("POST")
88+
.addHeader("Authorization", "Basic " + encodedAuth)
89+
.addQueryParam("grant_type", "password")
90+
.addQueryParam("username", authenticationData.getUsername())
91+
.addQueryParam("password", authenticationData.getPassword());
92+
}
93+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.serverlessworkflow.impl.executors.http.oauth;
18+
19+
import static io.serverlessworkflow.api.types.OAuth2AutenthicationData.OAuth2AutenthicationDataGrant.CLIENT_CREDENTIALS;
20+
import static io.serverlessworkflow.api.types.OAuth2AutenthicationData.OAuth2AutenthicationDataGrant.PASSWORD;
21+
22+
import io.serverlessworkflow.api.types.OAuth2AutenthicationData;
23+
import io.serverlessworkflow.api.types.Oauth2;
24+
25+
class ClientSecretPostStep {
26+
private final Oauth2 oauth2;
27+
28+
public ClientSecretPostStep(Oauth2 oauth2) {
29+
this.oauth2 = oauth2;
30+
}
31+
32+
public void execute(HttpRequestBuilder requestBuilder) {
33+
OAuth2AutenthicationData authenticationData =
34+
oauth2.getOAuth2ConnectAuthenticationProperties().getOAuth2AutenthicationData();
35+
36+
if (authenticationData.getGrant().equals(PASSWORD)) {
37+
password(requestBuilder);
38+
} else if (authenticationData.getGrant().equals(CLIENT_CREDENTIALS)) {
39+
clientCredentials(requestBuilder);
40+
} else {
41+
throw new UnsupportedOperationException(
42+
"Unsupported grant type: " + authenticationData.getGrant());
43+
}
44+
}
45+
46+
private void clientCredentials(HttpRequestBuilder requestBuilder) {
47+
OAuth2AutenthicationData authenticationData =
48+
oauth2.getOAuth2ConnectAuthenticationProperties().getOAuth2AutenthicationData();
49+
if (authenticationData.getClient() == null
50+
|| authenticationData.getClient().getId() == null
51+
|| authenticationData.getClient().getSecret() == null) {
52+
throw new IllegalArgumentException(
53+
"Client ID and secret must be provided for client authentication");
54+
}
55+
56+
requestBuilder
57+
.withMethod("POST")
58+
.addQueryParam("grant_type", "client_credentials")
59+
.addQueryParam("client_id", authenticationData.getClient().getId())
60+
.addQueryParam("client_secret", authenticationData.getClient().getSecret());
61+
}
62+
63+
private void password(HttpRequestBuilder requestBuilder) {
64+
OAuth2AutenthicationData authenticationData =
65+
oauth2.getOAuth2ConnectAuthenticationProperties().getOAuth2AutenthicationData();
66+
if (authenticationData.getUsername() == null || authenticationData.getPassword() == null) {
67+
throw new IllegalArgumentException(
68+
"Username and password must be provided for password grant type");
69+
}
70+
if (authenticationData.getClient() == null
71+
|| authenticationData.getClient().getId() == null
72+
|| authenticationData.getClient().getSecret() == null) {
73+
throw new IllegalArgumentException(
74+
"Client ID and secret must be provided for client authentication");
75+
}
76+
77+
requestBuilder
78+
.withMethod("POST")
79+
.addQueryParam("grant_type", "password")
80+
.addQueryParam("client_id", authenticationData.getClient().getId())
81+
.addQueryParam("client_secret", authenticationData.getClient().getSecret())
82+
.addQueryParam("username", authenticationData.getUsername())
83+
.addQueryParam("password", authenticationData.getPassword());
84+
}
85+
}

0 commit comments

Comments
 (0)