Skip to content

Commit b626e95

Browse files
committed
Improve OAuth2 DSL (FuncDSL and DSL)
Signed-off-by: Matheus Cruz <matheuscruz.dev@gmail.com>
1 parent cafd804 commit b626e95

3 files changed

Lines changed: 224 additions & 0 deletions

File tree

  • experimental/fluent/func/src
  • fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl

experimental/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/dsl/FuncDSL.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import io.serverlessworkflow.fluent.func.configurers.SwitchCaseConfigurer;
4040
import io.serverlessworkflow.fluent.spec.AbstractEventConsumptionStrategyBuilder;
4141
import io.serverlessworkflow.fluent.spec.EventFilterBuilder;
42+
import io.serverlessworkflow.fluent.spec.OAuth2AuthenticationPolicyBuilder;
43+
import io.serverlessworkflow.fluent.spec.OIDCBuilder.OAuth2AuthenticationPropertiesEndpointsBuilder;
4244
import io.serverlessworkflow.fluent.spec.ScheduleBuilder;
4345
import io.serverlessworkflow.fluent.spec.TimeoutBuilder;
4446
import io.serverlessworkflow.fluent.spec.WorkflowTaskBuilder;
@@ -2371,4 +2373,24 @@ public static AuthenticationConfigurer oauth2(
23712373
public static AuthenticationConfigurer oauth2(String secret) {
23722374
return DSL.oauth2(secret);
23732375
}
2376+
2377+
/**
2378+
* @see DSL#oauth2(String, OAuth2AuthenticationData.OAuth2AuthenticationDataGrant, String, String,
2379+
* Consumer)
2380+
*/
2381+
public static AuthenticationConfigurer oauth2(
2382+
String authority,
2383+
OAuth2AuthenticationData.OAuth2AuthenticationDataGrant grant,
2384+
String clientId,
2385+
String clientSecret,
2386+
Consumer<OAuth2AuthenticationPropertiesEndpointsBuilder> endpoints) {
2387+
return DSL.oauth2(authority, grant, clientId, clientSecret, endpoints);
2388+
}
2389+
2390+
/**
2391+
* @see DSL#oauth2(Consumer)
2392+
*/
2393+
public static AuthenticationConfigurer oauth2(Consumer<OAuth2AuthenticationPolicyBuilder> cfg) {
2394+
return DSL.oauth2(cfg);
2395+
}
23742396
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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.fluent.func;
17+
18+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.call;
19+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.http;
20+
import static io.serverlessworkflow.fluent.func.dsl.FuncDSL.oauth2;
21+
import static org.junit.jupiter.api.Assertions.assertEquals;
22+
import static org.junit.jupiter.api.Assertions.assertNotNull;
23+
24+
import io.serverlessworkflow.api.types.OAuth2AuthenticationData;
25+
import io.serverlessworkflow.api.types.OAuth2AuthenticationDataClient;
26+
import io.serverlessworkflow.api.types.OAuth2ConnectAuthenticationProperties;
27+
import io.serverlessworkflow.api.types.Workflow;
28+
import java.net.URI;
29+
import java.util.List;
30+
import org.junit.jupiter.api.Test;
31+
32+
class FuncDSLOAuth2Test {
33+
34+
private static final String EXPR_ENDPOINT = "${ .endpoint }";
35+
36+
private static OAuth2ConnectAuthenticationProperties oauth2PropertiesOf(Workflow wf) {
37+
var auth =
38+
wf.getDo()
39+
.get(0)
40+
.getTask()
41+
.getCallTask()
42+
.getCallHTTP()
43+
.getWith()
44+
.getEndpoint()
45+
.getEndpointConfiguration()
46+
.getAuthentication()
47+
.getAuthenticationPolicy();
48+
assertNotNull(auth.getOAuth2AuthenticationPolicy());
49+
return auth.getOAuth2AuthenticationPolicy()
50+
.getOauth2()
51+
.getOAuth2ConnectAuthenticationProperties();
52+
}
53+
54+
@Test
55+
void convenience_overload_sets_token_endpoint() {
56+
Workflow wf =
57+
FuncWorkflowBuilder.workflow("oauth2-token")
58+
.tasks(
59+
call(
60+
http()
61+
.POST()
62+
.endpoint(
63+
EXPR_ENDPOINT,
64+
oauth2(
65+
"https://auth.example.com/",
66+
OAuth2AuthenticationData.OAuth2AuthenticationDataGrant
67+
.CLIENT_CREDENTIALS,
68+
"client-id",
69+
"client-secret",
70+
e -> e.token("/custom/token")))))
71+
.build();
72+
73+
var props = oauth2PropertiesOf(wf);
74+
assertEquals(URI.create("https://auth.example.com/"), props.getAuthority().getLiteralUri());
75+
assertEquals(
76+
OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS,
77+
props.getGrant());
78+
assertEquals("client-id", props.getClient().getId());
79+
assertEquals("client-secret", props.getClient().getSecret());
80+
assertEquals("/custom/token", props.getEndpoints().getToken());
81+
}
82+
83+
@Test
84+
void builder_overload_supports_full_oauth2_section() {
85+
Workflow wf =
86+
FuncWorkflowBuilder.workflow("oauth2-full")
87+
.tasks(
88+
call(
89+
http()
90+
.GET()
91+
.endpoint(
92+
EXPR_ENDPOINT,
93+
oauth2(
94+
o ->
95+
o.endpoints(
96+
e ->
97+
e.token("/oauth2/token")
98+
.revocation("/oauth2/revoke")
99+
.introspection("/oauth2/introspect"))
100+
.authority("https://auth.example.com/")
101+
.grant(
102+
OAuth2AuthenticationData.OAuth2AuthenticationDataGrant
103+
.CLIENT_CREDENTIALS)
104+
.scopes("read", "write")
105+
.audiences("api://default")
106+
.client(
107+
c ->
108+
c.id("client-id")
109+
.secret("client-secret")
110+
.authentication(
111+
OAuth2AuthenticationDataClient
112+
.ClientAuthentication
113+
.CLIENT_SECRET_BASIC))))))
114+
.build();
115+
116+
var props = oauth2PropertiesOf(wf);
117+
assertEquals(URI.create("https://auth.example.com/"), props.getAuthority().getLiteralUri());
118+
assertEquals(
119+
OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS,
120+
props.getGrant());
121+
assertEquals(List.of("read", "write"), props.getScopes());
122+
assertEquals(List.of("api://default"), props.getAudiences());
123+
assertEquals("client-id", props.getClient().getId());
124+
assertEquals(
125+
OAuth2AuthenticationDataClient.ClientAuthentication.CLIENT_SECRET_BASIC,
126+
props.getClient().getAuthentication());
127+
assertEquals("/oauth2/token", props.getEndpoints().getToken());
128+
assertEquals("/oauth2/revoke", props.getEndpoints().getRevocation());
129+
assertEquals("/oauth2/introspect", props.getEndpoints().getIntrospection());
130+
}
131+
}

fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/dsl/DSL.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import io.serverlessworkflow.fluent.spec.EmitTaskBuilder;
2222
import io.serverlessworkflow.fluent.spec.EventFilterBuilder;
2323
import io.serverlessworkflow.fluent.spec.ForkTaskBuilder;
24+
import io.serverlessworkflow.fluent.spec.OAuth2AuthenticationPolicyBuilder;
25+
import io.serverlessworkflow.fluent.spec.OIDCBuilder.OAuth2AuthenticationPropertiesEndpointsBuilder;
2426
import io.serverlessworkflow.fluent.spec.ScheduleBuilder;
2527
import io.serverlessworkflow.fluent.spec.TaskItemListBuilder;
2628
import io.serverlessworkflow.fluent.spec.TimeoutBuilder;
@@ -480,6 +482,75 @@ public static AuthenticationConfigurer oauth2(String secret) {
480482
return a -> a.openIDConnect(o -> o.use(secret));
481483
}
482484

485+
/**
486+
* Build an OAuth2 authentication configurer with client credentials and explicit OAuth2 {@code
487+
* endpoints} (token, revocation, introspection).
488+
*
489+
* <p>Unlike the other {@code oauth2(...)} helpers, which delegate to OpenID Connect, this
490+
* overload produces a genuine OAuth2 authentication policy so that the OAuth2-specific {@code
491+
* endpoints} can be configured without hand-building the whole policy.
492+
*
493+
* <pre>{@code
494+
* oauth2(
495+
* "https://auth.example.com/",
496+
* OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS,
497+
* "client-id",
498+
* "client-secret",
499+
* e -> e.token("/custom/token"));
500+
* }</pre>
501+
*
502+
* @param authority OAuth2 authority/issuer URL
503+
* @param grant OAuth2 grant type
504+
* @param clientId client identifier
505+
* @param clientSecret client secret
506+
* @param endpoints consumer that configures the OAuth2 endpoints (e.g. {@code e -> e.token(...)})
507+
* @return an {@link AuthenticationConfigurer} configured as OAuth2 with custom endpoints
508+
*/
509+
public static AuthenticationConfigurer oauth2(
510+
String authority,
511+
OAuth2AuthenticationData.OAuth2AuthenticationDataGrant grant,
512+
String clientId,
513+
String clientSecret,
514+
Consumer<OAuth2AuthenticationPropertiesEndpointsBuilder> endpoints) {
515+
return a ->
516+
a.oauth2(
517+
o -> {
518+
o.authority(authority).grant(grant).client(c -> c.id(clientId).secret(clientSecret));
519+
o.endpoints(endpoints);
520+
});
521+
}
522+
523+
/**
524+
* Build a fully customizable OAuth2 authentication configurer.
525+
*
526+
* <p>This overload exposes the complete {@link OAuth2AuthenticationPolicyBuilder} so that every
527+
* field of the <a
528+
* href="https://github.com/serverlessworkflow/specification/blob/main/dsl-reference.md#oauth2-authentication">OAuth2
529+
* authentication</a> section of the Serverless Workflow DSL can be configured: {@code authority},
530+
* {@code grant}, {@code client}, {@code request} encoding, {@code issuers}, {@code scopes},
531+
* {@code audiences}, {@code username}, {@code password}, {@code subject}, {@code actor} and
532+
* {@code endpoints} (token, revocation, introspection).
533+
*
534+
* <p>Because the OAuth2-only {@code endpoints(...)} method is declared on {@link
535+
* OAuth2AuthenticationPolicyBuilder} (not on the shared {@link
536+
* io.serverlessworkflow.fluent.spec.OIDCBuilder}), call it first when chaining fluently:
537+
*
538+
* <pre>{@code
539+
* oauth2(o -> o.endpoints(e -> e.token("/custom/token"))
540+
* .authority("https://auth.example.com/")
541+
* .grant(OAuth2AuthenticationData.OAuth2AuthenticationDataGrant.CLIENT_CREDENTIALS)
542+
* .scopes("read", "write")
543+
* .audiences("api://default")
544+
* .client(c -> c.id("client-id").secret("client-secret")));
545+
* }</pre>
546+
*
547+
* @param cfg consumer that configures the OAuth2 authentication policy builder
548+
* @return an {@link AuthenticationConfigurer} configured as OAuth2
549+
*/
550+
public static AuthenticationConfigurer oauth2(Consumer<OAuth2AuthenticationPolicyBuilder> cfg) {
551+
return a -> a.oauth2(cfg);
552+
}
553+
483554
/**
484555
* Build a {@link RaiseSpec} for an error with a string type expression and HTTP status.
485556
*

0 commit comments

Comments
 (0)