Skip to content

Commit c70620e

Browse files
committed
feat: implement oauth2 client credentials auth profile
1 parent 7cdd35f commit c70620e

15 files changed

Lines changed: 437 additions & 142 deletions

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ repositories {
1616

1717
dependencies {
1818
implementation("com.fasterxml.jackson.core:jackson-databind:2.21.1")
19+
implementation("com.nimbusds:nimbus-jose-jwt:10.8")
1920
implementation("jakarta.ws.rs:jakarta.ws.rs-api:4.0.0")
2021

2122
testImplementation(platform("org.junit:junit-bom:6.0.3"))

src/main/java/org/eclipse/dataplane/Dataplane.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.eclipse.dataplane.domain.dataflow.DataFlowSuspendMessage;
2929
import org.eclipse.dataplane.domain.dataflow.DataFlowTerminateMessage;
3030
import org.eclipse.dataplane.domain.registration.Authorization;
31-
import org.eclipse.dataplane.domain.registration.AuthorizationType;
3231
import org.eclipse.dataplane.domain.registration.ControlPlaneRegistrationMessage;
3332
import org.eclipse.dataplane.domain.registration.DataPlaneRegistrationMessage;
3433
import org.eclipse.dataplane.logic.OnCompleted;
@@ -56,7 +55,6 @@
5655
import java.util.Map;
5756
import java.util.Set;
5857
import java.util.UUID;
59-
import java.util.function.BiConsumer;
6058

6159
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
6260
import static java.util.Collections.emptyMap;
@@ -79,7 +77,7 @@ public class Dataplane {
7977
private OnCompleted onCompleted = dataFlow -> Result.failure(new UnsupportedOperationException("onCompleted is not implemented"));
8078

8179
private final HttpClient httpClient = HttpClient.newHttpClient();
82-
private final Map<String, AuthorizationType> authorizationTypes = new HashMap<>();
80+
private final Map<String, Authorization> authorizations = new HashMap<>();
8381

8482
public static Builder newInstance() {
8583
return new Builder();
@@ -314,11 +312,10 @@ private Result<Void> notifyControlPlane(String action, DataFlow dataFlow, Object
314312

315313
var controlPlane = controlPlaneStore.findByEndpoint(dataFlow.getCallbackAddress());
316314
if (controlPlane.succeeded()) {
317-
var authorization = controlPlane.getContent().authorization();
318-
if (authorization != null) {
319-
var authorizationType = authorizationTypes.get(authorization.getType());
320-
var castAuthorization = objectMapper.convertValue(authorization, authorizationType.authorizationClass());
321-
authorizationType.authorizationFunction().accept(requestBuilder, castAuthorization);
315+
var authorizationProfile = controlPlane.getContent().authorization();
316+
if (authorizationProfile != null) {
317+
var authorization = authorizations.get(authorizationProfile.getType());
318+
authorization.apply(requestBuilder, authorizationProfile);
322319
}
323320
}
324321

@@ -348,7 +345,7 @@ public ControlPlaneStore controlPlaneStore() {
348345

349346
public Result<Void> registerControlPlane(ControlPlaneRegistrationMessage message) {
350347
for (var auth : message.authorization()) {
351-
if (!authorizationTypes.containsKey(auth.getType())) {
348+
if (!authorizations.containsKey(auth.getType())) {
352349
return Result.failure(new AuthorizationNotSupported(auth));
353350
}
354351
}
@@ -371,12 +368,14 @@ public static class Builder {
371368
private final Dataplane dataplane = new Dataplane();
372369

373370
private Builder() {
371+
374372
}
375373

376374
public Dataplane build() {
377375
if (dataplane.id == null) {
378376
dataplane.id = UUID.randomUUID().toString();
379377
}
378+
380379
return dataplane;
381380
}
382381

@@ -430,8 +429,8 @@ public Builder onTerminate(OnTerminate onTerminate) {
430429
return this;
431430
}
432431

433-
public <T extends Authorization> Builder registerAuthorization(String type, Class<T> authorizationClass, BiConsumer<HttpRequest.Builder, T> authorizationFunction) {
434-
dataplane.authorizationTypes.put(type, new AuthorizationType<>(type, authorizationClass, authorizationFunction));
432+
public Builder registerAuthorization(Authorization authorization) {
433+
dataplane.authorizations.put(authorization.type(), authorization);
435434
return this;
436435
}
437436
}

src/main/java/org/eclipse/dataplane/domain/controlplane/ControlPlane.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
package org.eclipse.dataplane.domain.controlplane;
1616

1717
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
18-
import org.eclipse.dataplane.domain.registration.Authorization;
19-
import org.eclipse.dataplane.domain.registration.RawAuthorization;
18+
import org.eclipse.dataplane.domain.registration.AuthorizationProfile;
2019

2120
import java.util.ArrayList;
2221
import java.util.List;
@@ -26,7 +25,7 @@ public class ControlPlane {
2625

2726
private String id;
2827
private String endpoint;
29-
private final List<RawAuthorization> authorizations = new ArrayList<>();
28+
private final List<AuthorizationProfile> authorizations = new ArrayList<>();
3029

3130
public String getId() {
3231
return id;
@@ -40,11 +39,11 @@ public static ControlPlane.Builder newInstance() {
4039
return new ControlPlane.Builder();
4140
}
4241

43-
public List<RawAuthorization> getAuthorizations() {
42+
public List<AuthorizationProfile> getAuthorizations() {
4443
return authorizations;
4544
}
4645

47-
public Authorization authorization() {
46+
public AuthorizationProfile authorization() {
4847
return getAuthorizations().stream().findAny().orElse(null);
4948
}
5049

@@ -72,7 +71,7 @@ public Builder endpoint(String endpoint) {
7271
return this;
7372
}
7473

75-
public Builder authorization(List<RawAuthorization> authorizations) {
74+
public Builder authorization(List<AuthorizationProfile> authorizations) {
7675
controlPlane.authorizations.addAll(authorizations);
7776
return this;
7877
}

src/main/java/org/eclipse/dataplane/domain/registration/Authorization.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,22 @@
1414

1515
package org.eclipse.dataplane.domain.registration;
1616

17+
import java.net.http.HttpRequest;
18+
19+
/**
20+
* Defines structure for an authorization profile.
21+
*/
1722
public interface Authorization {
1823

19-
String getType();
24+
/**
25+
* Return the authorization profile type string
26+
*/
27+
String type();
28+
29+
/**
30+
* Function that applies the authorization profile to the request builder.
31+
* e.g. the Authorization header could be added with proper content.
32+
*/
33+
HttpRequest.Builder apply(HttpRequest.Builder requestBuilder, AuthorizationProfile profile);
34+
2035
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (c) 2026 Think-it GmbH
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Think-it GmbH - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.dataplane.domain.registration;
16+
17+
import com.fasterxml.jackson.annotation.JsonAnyGetter;
18+
import com.fasterxml.jackson.annotation.JsonAnySetter;
19+
import org.eclipse.dataplane.port.exception.IllegalAttributeTypeException;
20+
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
24+
public class AuthorizationProfile {
25+
26+
private final Map<String, Object> attributes;
27+
28+
public AuthorizationProfile() {
29+
attributes = new HashMap<>();
30+
}
31+
32+
public AuthorizationProfile(String type) {
33+
this();
34+
attributes.put("type", type);
35+
}
36+
37+
public String getType() {
38+
return attributes.get("type").toString();
39+
}
40+
41+
@JsonAnyGetter
42+
public Map<String, Object> getAttributes() {
43+
return attributes;
44+
}
45+
46+
@JsonAnySetter
47+
public void setAttribute(String key, Object value) {
48+
attributes.put(key, value);
49+
}
50+
51+
public String stringAttribute(String key) {
52+
var attribute = attributes.get(key);
53+
if (attribute == null) {
54+
return null;
55+
}
56+
57+
if (attribute instanceof String stringAttribute) {
58+
return stringAttribute;
59+
}
60+
61+
throw new IllegalAttributeTypeException("Attribute %s is not a String but it's a %s".formatted(key, attribute.getClass().getSimpleName()));
62+
}
63+
64+
public AuthorizationProfile withAttribute(String key, String value) {
65+
setAttribute(key, value);
66+
return this;
67+
}
68+
}

src/main/java/org/eclipse/dataplane/domain/registration/ControlPlaneRegistrationMessage.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
public record ControlPlaneRegistrationMessage(
2222
String controlplaneId,
2323
String endpoint,
24-
List<RawAuthorization> authorization
24+
List<AuthorizationProfile> authorization
2525
) {
2626
public ControlPlaneRegistrationMessage(String controlplaneId, String endpoint) {
2727
this(controlplaneId, endpoint, emptyList());
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) 2026 Think-it GmbH
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Think-it GmbH - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.dataplane.domain.registration;
16+
17+
import com.fasterxml.jackson.databind.ObjectMapper;
18+
19+
import java.net.URI;
20+
import java.net.URLEncoder;
21+
import java.net.http.HttpClient;
22+
import java.net.http.HttpRequest;
23+
import java.net.http.HttpResponse;
24+
import java.nio.charset.StandardCharsets;
25+
import java.util.Map;
26+
import java.util.stream.Collectors;
27+
28+
import static jakarta.ws.rs.core.HttpHeaders.AUTHORIZATION;
29+
import static jakarta.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED;
30+
31+
public class Oauth2ClientCredentialsAuthorization implements Authorization {
32+
33+
private final HttpClient httpClient = HttpClient.newHttpClient();
34+
private final ObjectMapper objectMapper = new ObjectMapper();
35+
36+
@Override
37+
public String type() {
38+
return "oauth2_client_credentials";
39+
}
40+
41+
@Override
42+
public HttpRequest.Builder apply(HttpRequest.Builder requestBuilder, AuthorizationProfile profile) {
43+
var tokenEndpoint = profile.stringAttribute("tokenEndpoint");
44+
45+
var parameters = Map.of(
46+
"grant_type", "client_credentials",
47+
"client_id", profile.stringAttribute("clientId"),
48+
"client_secret", profile.stringAttribute("clientSecret")
49+
);
50+
51+
var form = parameters.entrySet()
52+
.stream()
53+
.map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
54+
.collect(Collectors.joining("&"));
55+
56+
57+
var request = HttpRequest.newBuilder(URI.create(tokenEndpoint))
58+
.POST(HttpRequest.BodyPublishers.ofString(form))
59+
.header("Content-Type", APPLICATION_FORM_URLENCODED)
60+
.build();
61+
62+
try {
63+
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
64+
var body = response.body();
65+
var accessToken = objectMapper.readValue(body, Map.class).get("access_token").toString();
66+
return requestBuilder.header(AUTHORIZATION, "Bearer " + accessToken);
67+
} catch (Exception e) {
68+
throw new RuntimeException(e);
69+
}
70+
71+
}
72+
}

src/main/java/org/eclipse/dataplane/domain/registration/RawAuthorization.java

Lines changed: 0 additions & 41 deletions
This file was deleted.

src/main/java/org/eclipse/dataplane/port/exception/AuthorizationNotSupported.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414

1515
package org.eclipse.dataplane.port.exception;
1616

17-
import org.eclipse.dataplane.domain.registration.Authorization;
17+
import org.eclipse.dataplane.domain.registration.AuthorizationProfile;
1818

1919
public class AuthorizationNotSupported extends Exception {
2020

21-
public AuthorizationNotSupported(Authorization authorization) {
22-
super("Authorization type " + authorization.getType() + " not supported");
21+
public AuthorizationNotSupported(AuthorizationProfile authorizationProfile) {
22+
super("Authorization type " + authorizationProfile.getType() + " not supported");
2323
}
2424

2525
}

src/main/java/org/eclipse/dataplane/domain/registration/AuthorizationType.java renamed to src/main/java/org/eclipse/dataplane/port/exception/IllegalAttributeTypeException.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,10 @@
1212
*
1313
*/
1414

15-
package org.eclipse.dataplane.domain.registration;
16-
17-
import java.net.http.HttpRequest;
18-
import java.util.function.BiConsumer;
19-
20-
public record AuthorizationType<T extends Authorization>(
21-
String type,
22-
Class<T> authorizationClass,
23-
BiConsumer<HttpRequest.Builder, T> authorizationFunction // TODO: dedicated interface
24-
) {
15+
package org.eclipse.dataplane.port.exception;
2516

17+
public class IllegalAttributeTypeException extends RuntimeException {
18+
public IllegalAttributeTypeException(String message) {
19+
super(message);
20+
}
2621
}

0 commit comments

Comments
 (0)