Skip to content

Commit e21f4c0

Browse files
committed
Provide revoke and introspection methods to AccessTokenProvider
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
1 parent d0327cb commit e21f4c0

13 files changed

Lines changed: 520 additions & 64 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,7 @@ build/
3232
.vscode/
3333

3434
# Bob-Shell
35-
.bob/notes/
35+
.bob/notes/
36+
37+
38+
impl/test/*/**.db

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,45 @@
2121

2222
public interface AccessTokenProvider {
2323
JWT validateAndGet(WorkflowContext workflow, TaskContext context, WorkflowModel model);
24+
25+
/**
26+
* Introspects the given token against the configured introspection endpoint, as defined by <a
27+
* href="https://www.rfc-editor.org/rfc/rfc7662">RFC 7662</a>.
28+
*
29+
* <p>This is an optional capability. The default implementation throws {@link
30+
* UnsupportedOperationException}; providers backed by an introspection-capable OIDC client should
31+
* override it.
32+
*
33+
* @param tokenTypeHint optional {@code token_type_hint} (e.g. {@code access_token}), may be
34+
* {@code null}
35+
*/
36+
default TokenIntrospection introspect(
37+
WorkflowContext workflow,
38+
TaskContext context,
39+
WorkflowModel model,
40+
String token,
41+
String tokenTypeHint) {
42+
throw new UnsupportedOperationException(
43+
"Token introspection is not supported by this provider");
44+
}
45+
46+
/**
47+
* Revokes the given token against the configured revocation endpoint, as defined by <a
48+
* href="https://www.rfc-editor.org/rfc/rfc7009">RFC 7009</a>.
49+
*
50+
* <p>This is an optional capability. The default implementation throws {@link
51+
* UnsupportedOperationException}; providers backed by a revocation-capable OIDC client should
52+
* override it.
53+
*
54+
* @param tokenTypeHint optional {@code token_type_hint} (e.g. {@code access_token}, {@code
55+
* refresh_token}), may be {@code null}
56+
*/
57+
default void revoke(
58+
WorkflowContext workflow,
59+
TaskContext context,
60+
WorkflowModel model,
61+
String token,
62+
String tokenTypeHint) {
63+
throw new UnsupportedOperationException("Token revocation is not supported by this provider");
64+
}
2465
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ protected ClientSecretPost(
3838
protected void clientCredentials(OAuth2AuthenticationData authenticationData) {
3939
requestBuilder
4040
.withGrantType(authenticationData.getGrant().value())
41-
.addQueryParam(
41+
.addClientAuthParam(
4242
"client_id",
4343
WorkflowUtils.buildStringFilter(application, authenticationData.getClient().getId()))
44-
.addQueryParam(
44+
.addClientAuthParam(
4545
"client_secret",
4646
WorkflowUtils.buildStringFilter(
4747
application, authenticationData.getClient().getSecret()));
@@ -64,8 +64,8 @@ protected void clientCredentials(Map<String, Object> secret) {
6464
Map<String, Object> client = (Map<String, Object>) secret.get(CLIENT);
6565
requestBuilder
6666
.withGrantType((String) secret.get(GRANT))
67-
.addQueryParam("client_id", (String) client.get(ID))
68-
.addQueryParam("client_secret", (String) client.get(SECRET));
67+
.addClientAuthParam("client_id", (String) client.get(ID))
68+
.addClientAuthParam("client_secret", (String) client.get(SECRET));
6969
}
7070

7171
@Override

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
import io.serverlessworkflow.impl.WorkflowValueResolver;
1919
import java.net.URI;
2020
import java.util.Map;
21+
import java.util.Optional;
2122

2223
public record HttpRequestInfo(
2324
Map<String, WorkflowValueResolver<String>> headers,
2425
Map<String, WorkflowValueResolver<String>> queryParams,
26+
Map<String, WorkflowValueResolver<String>> clientAuthParams,
2527
WorkflowValueResolver<URI> uri,
26-
WorkflowValueResolver<URI> revocationUri,
27-
WorkflowValueResolver<URI> introspectionUri,
28+
Optional<WorkflowValueResolver<URI>> revocationUri,
29+
Optional<WorkflowValueResolver<URI>> introspectionUri,
2830
String grantType,
2931
String contentType) {}

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

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,21 @@
2323
import java.util.HashMap;
2424
import java.util.Map;
2525
import java.util.Objects;
26+
import java.util.Optional;
2627

2728
class HttpRequestInfoBuilder {
2829

2930
private Map<String, WorkflowValueResolver<String>> headers;
3031

3132
private Map<String, WorkflowValueResolver<String>> queryParams;
3233

34+
private Map<String, WorkflowValueResolver<String>> clientAuthParams;
35+
3336
private WorkflowValueResolver<URI> uri;
3437

35-
private WorkflowValueResolver<URI> revocationUri;
38+
private Optional<WorkflowValueResolver<URI>> revocationUri = Optional.empty();
3639

37-
private WorkflowValueResolver<URI> introspectionUri;
40+
private Optional<WorkflowValueResolver<URI>> introspectionUri = Optional.empty();
3841

3942
private String grantType;
4043

@@ -43,6 +46,7 @@ class HttpRequestInfoBuilder {
4346
HttpRequestInfoBuilder() {
4447
headers = new HashMap<>();
4548
queryParams = new HashMap<>();
49+
clientAuthParams = new HashMap<>();
4650
}
4751

4852
HttpRequestInfoBuilder addHeader(String key, String token) {
@@ -65,17 +69,28 @@ HttpRequestInfoBuilder addQueryParam(String key, WorkflowValueResolver<String> t
6569
return this;
6670
}
6771

72+
HttpRequestInfoBuilder addClientAuthParam(String key, String token) {
73+
clientAuthParams.put(key, (w, t, m) -> token);
74+
return this;
75+
}
76+
77+
HttpRequestInfoBuilder addClientAuthParam(String key, WorkflowValueResolver<String> token) {
78+
clientAuthParams.put(key, token);
79+
return this;
80+
}
81+
6882
HttpRequestInfoBuilder withUri(WorkflowValueResolver<URI> uri) {
6983
this.uri = uri;
7084
return this;
7185
}
7286

73-
HttpRequestInfoBuilder withRevocationUri(WorkflowValueResolver<URI> revocationUri) {
87+
HttpRequestInfoBuilder withRevocationUri(Optional<WorkflowValueResolver<URI>> revocationUri) {
7488
this.revocationUri = revocationUri;
7589
return this;
7690
}
7791

78-
HttpRequestInfoBuilder withIntrospectionUri(WorkflowValueResolver<URI> introspectionUri) {
92+
HttpRequestInfoBuilder withIntrospectionUri(
93+
Optional<WorkflowValueResolver<URI>> introspectionUri) {
7994
this.introspectionUri = introspectionUri;
8095
return this;
8196
}
@@ -106,6 +121,13 @@ HttpRequestInfo build() {
106121
contentType = APPLICATION_X_WWW_FORM_URLENCODED.value();
107122
}
108123
return new HttpRequestInfo(
109-
headers, queryParams, uri, revocationUri, introspectionUri, grantType, contentType);
124+
headers,
125+
queryParams,
126+
clientAuthParams,
127+
uri,
128+
revocationUri,
129+
introspectionUri,
130+
grantType,
131+
contentType);
110132
}
111133
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,13 @@ protected void password(Map<String, Object> secret) {
115115

116116
private void addAssertion(String clientId, String assertion) {
117117
if (clientId != null) {
118-
requestBuilder.addQueryParam(
118+
requestBuilder.addClientAuthParam(
119119
CLIENT_ID, WorkflowUtils.buildStringFilter(application, clientId));
120120
}
121121
requestBuilder
122-
.addQueryParam(CLIENT_ASSERTION_TYPE, JWT_BEARER_ASSERTION_TYPE)
123-
.addQueryParam(CLIENT_ASSERTION, WorkflowUtils.buildStringFilter(application, assertion));
122+
.addClientAuthParam(CLIENT_ASSERTION_TYPE, JWT_BEARER_ASSERTION_TYPE)
123+
.addClientAuthParam(
124+
CLIENT_ASSERTION, WorkflowUtils.buildStringFilter(application, assertion));
124125
}
125126

126127
@SuppressWarnings("unchecked")

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@
2525
import io.serverlessworkflow.impl.WorkflowValueResolver;
2626
import java.net.URI;
2727
import java.util.Map;
28+
import java.util.Optional;
2829

2930
class OAuthRequestBuilder
3031
extends AbstractAuthRequestBuilder<OAuth2ConnectAuthenticationProperties> {
3132

32-
private static String DEFAULT_TOKEN_PATH = "oauth2/token";
33-
private static String DEFAULT_REVOCATION_PATH = "oauth2/revoke";
34-
private static String DEFAULT_INTROSPECTION_PATH = "oauth2/introspect";
33+
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";
3536

3637
public OAuthRequestBuilder(WorkflowApplication application) {
3738
super(application);
@@ -47,9 +48,11 @@ protected void authenticationURI(OAuth2ConnectAuthenticationProperties authentic
4748
String introspection = endpoints != null ? endpoints.getIntrospection() : null;
4849
requestBuilder
4950
.withUri(endpointResolver(uri, endpointPath(token, DEFAULT_TOKEN_PATH)))
50-
.withRevocationUri(endpointResolver(uri, endpointPath(revocation, DEFAULT_REVOCATION_PATH)))
51+
.withRevocationUri(
52+
Optional.of(endpointResolver(uri, endpointPath(revocation, DEFAULT_REVOCATION_PATH))))
5153
.withIntrospectionUri(
52-
endpointResolver(uri, endpointPath(introspection, DEFAULT_INTROSPECTION_PATH)));
54+
Optional.of(
55+
endpointResolver(uri, endpointPath(introspection, DEFAULT_INTROSPECTION_PATH))));
5356
}
5457

5558
@Override
@@ -59,9 +62,11 @@ protected void authenticationURI(Map<String, Object> secret) {
5962
secret.get("endpoints") instanceof Map<?, ?> raw ? (Map<?, ?>) raw : Map.of();
6063
requestBuilder
6164
.withUri(staticUri(authority, endpoints, "token", DEFAULT_TOKEN_PATH))
62-
.withRevocationUri(staticUri(authority, endpoints, "revocation", DEFAULT_REVOCATION_PATH))
65+
.withRevocationUri(
66+
Optional.of(staticUri(authority, endpoints, "revocation", DEFAULT_REVOCATION_PATH)))
6367
.withIntrospectionUri(
64-
staticUri(authority, endpoints, "introspection", DEFAULT_INTROSPECTION_PATH));
68+
Optional.of(
69+
staticUri(authority, endpoints, "introspection", DEFAULT_INTROSPECTION_PATH)));
6570
}
6671

6772
private static String endpointPath(String path, String defaultPath) {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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 java.util.Map;
19+
20+
/**
21+
* The result of an OAuth2 token introspection request as defined by <a
22+
* href="https://www.rfc-editor.org/rfc/rfc7662">RFC 7662</a>.
23+
*
24+
* <p>{@code active} is the only field guaranteed by the specification; the full response is exposed
25+
* through {@code claims} so callers can inspect additional metadata (scope, exp, sub, ...).
26+
*/
27+
public record TokenIntrospection(boolean active, Map<String, Object> claims) {}

impl/http/pom.xml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,35 @@
1212
<groupId>io.serverlessworkflow</groupId>
1313
<artifactId>serverlessworkflow-impl-template-resolver</artifactId>
1414
</dependency>
15+
<dependency>
16+
<groupId>org.junit.jupiter</groupId>
17+
<artifactId>junit-jupiter-api</artifactId>
18+
<scope>test</scope>
19+
</dependency>
20+
<dependency>
21+
<groupId>org.junit.jupiter</groupId>
22+
<artifactId>junit-jupiter-engine</artifactId>
23+
<scope>test</scope>
24+
</dependency>
25+
<dependency>
26+
<groupId>org.mockito</groupId>
27+
<artifactId>mockito-core</artifactId>
28+
<scope>test</scope>
29+
</dependency>
30+
<dependency>
31+
<groupId>com.squareup.okhttp3</groupId>
32+
<artifactId>mockwebserver</artifactId>
33+
<scope>test</scope>
34+
</dependency>
35+
<dependency>
36+
<groupId>org.glassfish.jersey.core</groupId>
37+
<artifactId>jersey-client</artifactId>
38+
<scope>test</scope>
39+
</dependency>
40+
<dependency>
41+
<groupId>org.glassfish.jersey.media</groupId>
42+
<artifactId>jersey-media-json-jackson</artifactId>
43+
<scope>test</scope>
44+
</dependency>
1545
</dependencies>
1646
</project>

0 commit comments

Comments
 (0)