Skip to content

Commit deac24e

Browse files
committed
Apply pull request suggestions
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
1 parent aa642d0 commit deac24e

16 files changed

Lines changed: 199 additions & 88 deletions

impl/core/src/main/java/io/serverlessworkflow/impl/auth/AbstractAuthRequestBuilder.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ private void tokenParam(String tokenKey, String typeKey, OAuth2TokenDefinition d
130130
requestBuilder
131131
.addQueryParam(
132132
tokenKey, WorkflowUtils.buildStringFilter(application, definition.getToken()))
133-
.addQueryParam(typeKey, definition.getType());
133+
.addQueryParam(
134+
typeKey, WorkflowUtils.buildStringFilter(application, definition.getType()));
134135
}
135136
}
136137

@@ -142,8 +143,11 @@ protected void subjectActor(Map<String, Object> secret) {
142143
private void tokenParam(String tokenKey, String typeKey, Object rawDefinition) {
143144
if (rawDefinition instanceof Map<?, ?> definition) {
144145
requestBuilder
145-
.addQueryParam(tokenKey, (String) definition.get(TOKEN))
146-
.addQueryParam(typeKey, (String) definition.get(TYPE));
146+
.addQueryParam(
147+
tokenKey,
148+
WorkflowUtils.buildStringFilter(application, (String) definition.get(TOKEN)))
149+
.addQueryParam(
150+
typeKey, WorkflowUtils.buildStringFilter(application, (String) definition.get(TYPE)));
147151
}
148152
}
149153

impl/core/src/main/java/io/serverlessworkflow/impl/auth/JwtClientAssertion.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ void accept(Map<String, Object> secret) {
7373
"A client assertion must be provided for JWT client authentication");
7474
}
7575
if (PASSWORD.value().equals(secret.get(GRANT))) {
76+
if (secret.get(USER) == null || secret.get(AuthUtils.PASSWORD) == null) {
77+
throw new IllegalArgumentException(
78+
"Username and password must be provided for password grant type");
79+
}
7680
password(secret);
7781
} else {
7882
clientCredentials(secret);
@@ -126,6 +130,10 @@ private void addAssertion(String clientId, String assertion) {
126130

127131
@SuppressWarnings("unchecked")
128132
private static Map<String, Object> asClient(Map<String, Object> secret) {
129-
return (Map<String, Object>) secret.get(CLIENT);
133+
Object client = secret.get(CLIENT);
134+
if (client != null && !(client instanceof Map<?, ?>)) {
135+
throw new IllegalArgumentException("The 'client' entry must be a map");
136+
}
137+
return (Map<String, Object>) client;
130138
}
131139
}

impl/core/src/main/java/io/serverlessworkflow/impl/auth/OAuthRequestBuilder.java

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ class OAuthRequestBuilder
3131
extends AbstractAuthRequestBuilder<OAuth2ConnectAuthenticationProperties> {
3232

3333
private static final String DEFAULT_TOKEN_PATH = "oauth2/token";
34-
private static final String DEFAULT_REVOCATION_PATH = "oauth2/revoke";
35-
private static final String DEFAULT_INTROSPECTION_PATH = "oauth2/introspect";
3634

3735
public OAuthRequestBuilder(WorkflowApplication application) {
3836
super(application);
@@ -48,11 +46,8 @@ protected void authenticationURI(OAuth2ConnectAuthenticationProperties authentic
4846
String introspection = endpoints != null ? endpoints.getIntrospection() : null;
4947
requestBuilder
5048
.withUri(endpointResolver(uri, endpointPath(token, DEFAULT_TOKEN_PATH)))
51-
.withRevocationUri(
52-
Optional.of(endpointResolver(uri, endpointPath(revocation, DEFAULT_REVOCATION_PATH))))
53-
.withIntrospectionUri(
54-
Optional.of(
55-
endpointResolver(uri, endpointPath(introspection, DEFAULT_INTROSPECTION_PATH))));
49+
.withRevocationUri(optionalEndpoint(uri, revocation))
50+
.withIntrospectionUri(optionalEndpoint(uri, introspection));
5651
}
5752

5853
@Override
@@ -61,28 +56,44 @@ protected void authenticationURI(Map<String, Object> secret) {
6156
Map<?, ?> endpoints =
6257
secret.get("endpoints") instanceof Map<?, ?> raw ? (Map<?, ?>) raw : Map.of();
6358
requestBuilder
64-
.withUri(staticUri(authority, endpoints, "token", DEFAULT_TOKEN_PATH))
65-
.withRevocationUri(
66-
Optional.of(staticUri(authority, endpoints, "revocation", DEFAULT_REVOCATION_PATH)))
67-
.withIntrospectionUri(
68-
Optional.of(
69-
staticUri(authority, endpoints, "introspection", DEFAULT_INTROSPECTION_PATH)));
59+
.withUri(
60+
staticUri(authority, endpointPath((String) endpoints.get("token"), DEFAULT_TOKEN_PATH)))
61+
.withRevocationUri(optionalStaticUri(authority, endpoints.get("revocation")))
62+
.withIntrospectionUri(optionalStaticUri(authority, endpoints.get("introspection")));
63+
}
64+
65+
private static String stripLeadingSlash(String path) {
66+
return path.startsWith("/") ? path.substring(1) : path;
7067
}
7168

7269
private static String endpointPath(String path, String defaultPath) {
73-
return path != null ? path.replaceAll("^/", "") : defaultPath;
70+
return path != null ? stripLeadingSlash(path) : defaultPath;
7471
}
7572

7673
private WorkflowValueResolver<URI> endpointResolver(
7774
WorkflowValueResolver<URI> authority, String path) {
7875
return (w, t, m) -> concatURI(authority.apply(w, t, m), path);
7976
}
8077

81-
private static WorkflowValueResolver<URI> staticUri(
82-
URI authority, Map<?, ?> endpoints, String key, String defaultPath) {
83-
String path =
84-
endpoints.get(key) instanceof String value ? endpointPath(value, defaultPath) : defaultPath;
78+
// Revocation and introspection are optional capabilities: they are only wired up when the
79+
// workflow (or secret) explicitly declares the corresponding endpoint, so that providers without
80+
// these endpoints fail with a clear "not configured" error instead of calling a guessed path.
81+
private Optional<WorkflowValueResolver<URI>> optionalEndpoint(
82+
WorkflowValueResolver<URI> authority, String path) {
83+
return path == null
84+
? Optional.empty()
85+
: Optional.of(endpointResolver(authority, stripLeadingSlash(path)));
86+
}
87+
88+
private static WorkflowValueResolver<URI> staticUri(URI authority, String path) {
8589
URI uri = concatURI(authority, path);
8690
return (w, t, m) -> uri;
8791
}
92+
93+
private static Optional<WorkflowValueResolver<URI>> optionalStaticUri(
94+
URI authority, Object path) {
95+
return path instanceof String value
96+
? Optional.of(staticUri(authority, stripLeadingSlash(value)))
97+
: Optional.empty();
98+
}
8899
}

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/auth/JaxRSAccessTokenProvider.java

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,12 @@ public TokenIntrospection introspect(
8585
return execute(
8686
context,
8787
() -> {
88-
Response response =
89-
postManagementRequest(workflow, context, model, uri, token, tokenTypeHint);
90-
ensureSuccessful(response, context, "introspect token");
91-
Map<String, Object> body = response.readEntity(new GenericType<>() {});
92-
return new TokenIntrospection(Boolean.TRUE.equals(body.get("active")), body);
88+
try (Response response =
89+
postManagementRequest(workflow, context, model, uri, token, tokenTypeHint)) {
90+
ensureSuccessful(response, context, "introspect token");
91+
Map<String, Object> body = response.readEntity(new GenericType<>() {});
92+
return new TokenIntrospection(Boolean.TRUE.equals(body.get("active")), body);
93+
}
9394
});
9495
}
9596

@@ -105,10 +106,11 @@ public void revoke(
105106
context,
106107
() -> {
107108
// RFC 7009: a successful revocation responds with HTTP 200 and an empty body.
108-
Response response =
109-
postManagementRequest(workflow, context, model, uri, token, tokenTypeHint);
110-
ensureSuccessful(response, context, "revoke token");
111-
return null;
109+
try (Response response =
110+
postManagementRequest(workflow, context, model, uri, token, tokenTypeHint)) {
111+
ensureSuccessful(response, context, "revoke token");
112+
return null;
113+
}
112114
});
113115
}
114116

@@ -117,9 +119,10 @@ private Map<String, Object> invoke(
117119
return execute(
118120
taskContext,
119121
() -> {
120-
Response response = executeRequest(workflowContext, taskContext, model);
121-
ensureSuccessful(response, taskContext, "obtain token");
122-
return response.readEntity(new GenericType<>() {});
122+
try (Response response = executeRequest(workflowContext, taskContext, model)) {
123+
ensureSuccessful(response, taskContext, "obtain token");
124+
return response.readEntity(new GenericType<>() {});
125+
}
123126
});
124127
}
125128

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+
package io.serverlessworkflow.impl.auth;
17+
18+
import static io.serverlessworkflow.api.types.OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS;
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertFalse;
21+
import static org.junit.jupiter.api.Assertions.assertTrue;
22+
23+
import io.serverlessworkflow.api.types.OAuth2AuthenticationDataClient;
24+
import io.serverlessworkflow.api.types.OAuth2AuthenticationPropertiesEndpoints;
25+
import io.serverlessworkflow.api.types.OAuth2ConnectAuthenticationProperties;
26+
import io.serverlessworkflow.api.types.UriTemplate;
27+
import io.serverlessworkflow.impl.WorkflowApplication;
28+
import java.net.URI;
29+
import org.junit.jupiter.api.Test;
30+
31+
class OAuthRequestBuilderTest {
32+
33+
private static final URI AUTHORITY = URI.create("http://localhost:8888/realms/test-realm");
34+
35+
@Test
36+
void resolvesConfiguredEndpointsAndNormalizesLeadingSlash() {
37+
OAuth2ConnectAuthenticationProperties props = baseProperties();
38+
props.withEndpoints(
39+
new OAuth2AuthenticationPropertiesEndpoints()
40+
.withToken("protocol/openid-connect/token")
41+
// Leading slash must be stripped before being concatenated to the authority.
42+
.withRevocation("/protocol/openid-connect/revoke")
43+
.withIntrospection("protocol/openid-connect/introspect"));
44+
45+
HttpRequestInfo info = build(props);
46+
47+
assertEquals(
48+
URI.create("http://localhost:8888/realms/test-realm/protocol/openid-connect/token"),
49+
info.uri().apply(null, null, null));
50+
assertTrue(info.revocationUri().isPresent());
51+
assertEquals(
52+
URI.create("http://localhost:8888/realms/test-realm/protocol/openid-connect/revoke"),
53+
info.revocationUri().get().apply(null, null, null));
54+
assertTrue(info.introspectionUri().isPresent());
55+
assertEquals(
56+
URI.create("http://localhost:8888/realms/test-realm/protocol/openid-connect/introspect"),
57+
info.introspectionUri().get().apply(null, null, null));
58+
}
59+
60+
@Test
61+
void appliesDefaultTokenPathAndLeavesManagementEndpointsUnconfigured() {
62+
HttpRequestInfo info = build(baseProperties());
63+
64+
assertEquals(
65+
URI.create("http://localhost:8888/realms/test-realm/oauth2/token"),
66+
info.uri().apply(null, null, null));
67+
assertFalse(info.revocationUri().isPresent());
68+
assertFalse(info.introspectionUri().isPresent());
69+
}
70+
71+
private static OAuth2ConnectAuthenticationProperties baseProperties() {
72+
OAuth2ConnectAuthenticationProperties props = new OAuth2ConnectAuthenticationProperties();
73+
props.withAuthority(new UriTemplate().withLiteralUri(AUTHORITY));
74+
props.withGrant(CLIENT_CREDENTIALS);
75+
props.withClient(
76+
new OAuth2AuthenticationDataClient().withId("serverless-workflow").withSecret("secret"));
77+
return props;
78+
}
79+
80+
private static HttpRequestInfo build(OAuth2ConnectAuthenticationProperties props) {
81+
try (WorkflowApplication app = WorkflowApplication.builder().build()) {
82+
return new OAuthRequestBuilder(app).apply(props);
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)