Skip to content

Commit 7200e28

Browse files
authored
Support OAuth2 (#127)
Signed-off-by: AlbertWaninge <[email protected]>
1 parent c9ec6ca commit 7200e28

File tree

21 files changed

+287
-118
lines changed

21 files changed

+287
-118
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Changelog
2+
3+
Starting from version 3.0.0, all notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [3.0.0]
9+
10+
### Added
11+
12+
- Support for authorization when sending UFTP messages. This entails:
13+
- the addition of the ParticipantAuthorizationProvider interface that is used to take care of the authorization
14+
- a stub implementation of ParticipantAuthorizationProvider in the spring-module, which throws an exception when called
15+
- the extension of UftpParticipantInformation with a property that tells whether authorization is required
16+
- see README.md for more details
17+
18+

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,3 +411,18 @@ Useful tools for creating a key pair, signing and verifying UFTP messages:
411411

412412
Each payload message `*.xml` has a `*Signed.xml` counterpart which contains the same message but
413413
signed (encrypted using the private key) in a `SignedMessage`.
414+
415+
## Sending messages to participants that require authorization
416+
Starting from version 3.0.0, the Shapeshifter library supports sending messages to participants that require
417+
authorization. This means that the sender must have a valid token to send a message to the recipient.
418+
419+
Your implementation of `UftpParticipantService`, which returns `UftpParticipantInformation` instances, must tell
420+
whether the UFTP participant requires authorization in order to send messages to it.
421+
The `UftpSendMessageService` will check whether the recipient requires authorization and, if so, will call the
422+
`ParticipantAuthorizationProvider` to take care of the authorization for that participant and receive the Authorization
423+
header. If you are using the shapeshifter-spring library, a stub implementation of the `ParticipantAuthorizationProvider`
424+
that throws UnsupportedOperationException is provided. This is enough when you have no participants that require
425+
authorization. If you don't use the spring library or you have participants that require authorization, you must provide
426+
your own implementation of the `ParticipantAuthorizationProvider`.
427+
428+

api/pom.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.lfenergy.shapeshifter</groupId>
77
<artifactId>shapeshifter-library</artifactId>
8-
<version>2.7.0-SNAPSHOT</version>
8+
<version>3.0.0-SNAPSHOT</version>
99
<relativePath>../pom.xml</relativePath>
1010
</parent>
1111

@@ -46,6 +46,11 @@
4646
<artifactId>assertj-core</artifactId>
4747
<scope>test</scope>
4848
</dependency>
49+
<dependency>
50+
<groupId>net.datafaker</groupId>
51+
<artifactId>datafaker</artifactId>
52+
<scope>test</scope>
53+
</dependency>
4954
</dependencies>
5055

5156
<build>

api/src/main/java/org/lfenergy/shapeshifter/api/model/UftpParticipantInformation.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,13 @@
44

55
package org.lfenergy.shapeshifter.api.model;
66

7-
public record UftpParticipantInformation(String domain, String publicKey, String endpoint) {}
7+
/**
8+
* Data transfer object for UFTP participant information.
9+
*
10+
* @param domain The domain of the participant. It is not not be confused with the 'endpoint' for UFTP communication,
11+
* but rather serves as an identifier for the participant.
12+
* @param publicKey The public key of the participant.
13+
* @param endpoint The endpoint of the participant. This is the URL where the participant can be reached.
14+
* @param requiresAuthorization Specifies whether the participant requires authorization to communicate with it.
15+
*/
16+
public record UftpParticipantInformation(String domain, String publicKey, String endpoint, boolean requiresAuthorization) {}

api/src/test/java/org/lfenergy/shapeshifter/api/model/UftpParticipantInformationTest.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,28 @@
66

77
import static org.assertj.core.api.Assertions.assertThat;
88

9+
import net.bytebuddy.utility.RandomString;
910
import org.junit.jupiter.api.Test;
11+
import org.junit.platform.commons.util.StringUtils;
12+
13+
import java.util.Random;
1014

1115
class UftpParticipantInformationTest {
1216

13-
public static final String DOMAIN = "DOMAIN";
14-
public static final String ENDPOINT = "ENDPOINT";
15-
public static final String PUBLIC_KEY = "PUBLIC_KEY";
1617

1718
@Test
1819
void construction() {
19-
var testSubject = new UftpParticipantInformation(DOMAIN, PUBLIC_KEY, ENDPOINT);
20+
Random random = new Random();
21+
String domain = "DOMAIN" + random.nextInt();
22+
String endpoint = "ENDPOINT" + random.nextInt();
23+
String publicKey = "PUBLIC_KEY" + random.nextInt();
24+
boolean requiresAuth = random.nextBoolean();
25+
26+
var testSubject = new UftpParticipantInformation(domain, publicKey, endpoint, requiresAuth);
2027

21-
assertThat(testSubject.domain()).isEqualTo(DOMAIN);
22-
assertThat(testSubject.publicKey()).isEqualTo(PUBLIC_KEY);
23-
assertThat(testSubject.endpoint()).isEqualTo(ENDPOINT);
28+
assertThat(testSubject.domain()).isEqualTo(domain);
29+
assertThat(testSubject.publicKey()).isEqualTo(publicKey);
30+
assertThat(testSubject.endpoint()).isEqualTo(endpoint);
31+
assertThat(testSubject.requiresAuthorization()).isEqualTo(requiresAuth);
2432
}
2533
}

core/pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.lfenergy.shapeshifter</groupId>
77
<artifactId>shapeshifter-library</artifactId>
8-
<version>2.7.0-SNAPSHOT</version>
8+
<version>3.0.0-SNAPSHOT</version>
99
<relativePath>../pom.xml</relativePath>
1010
</parent>
1111

@@ -129,6 +129,12 @@
129129
<artifactId>assertj-core</artifactId>
130130
<scope>test</scope>
131131
</dependency>
132+
<dependency>
133+
<groupId>net.datafaker</groupId>
134+
<artifactId>datafaker</artifactId>
135+
<scope>test</scope>
136+
</dependency>
137+
132138
<!-- Configure a test application without UftpConnector mapping test beans -->
133139
<dependency>
134140
<groupId>org.wiremock</groupId>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.lfenergy.shapeshifter.core.service;
2+
3+
import org.lfenergy.shapeshifter.core.model.UftpParticipant;
4+
5+
/**
6+
* The ParticipantAuthorizationProvider provides a method to get the Authorization header value for a given participant.
7+
*/
8+
public interface ParticipantAuthorizationProvider {
9+
10+
/**
11+
* Method that returns the complete header value of the 'Authorization' header for the given participant.
12+
*
13+
* @param participant The participant for whom the Authorization header is requested
14+
*
15+
*/
16+
String getAuthorizationHeader(UftpParticipant participant);
17+
18+
}

core/src/main/java/org/lfenergy/shapeshifter/core/service/participant/ParticipantResolutionService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ public String getPublicKey(USEFRoleType senderRole, String senderDomain) {
2424
return getDomain(senderRole, senderDomain).publicKey();
2525
}
2626

27+
public UftpParticipantInformation getParticipantInformation(UftpParticipant recipient) {
28+
return getDomain(recipient.role(), recipient.domain());
29+
}
30+
2731
private UftpParticipantInformation getDomain(USEFRoleType role, String domain) {
2832
return uftpParticipantService.getParticipantInformation(role, domain).orElseThrow(
2933
() -> new UftpConnectorException("No participant found for " + domain + " in " + role));

core/src/main/java/org/lfenergy/shapeshifter/core/service/sending/UftpSendMessageService.java

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@
1313
import java.net.http.HttpResponse.BodyHandlers;
1414
import java.text.MessageFormat;
1515
import java.util.EnumSet;
16+
import java.util.HashMap;
17+
import java.util.Map;
1618
import java.util.Set;
1719

1820
import lombok.NonNull;
1921
import lombok.extern.apachecommons.CommonsLog;
2022
import org.lfenergy.shapeshifter.api.PayloadMessageResponseType;
2123
import org.lfenergy.shapeshifter.api.PayloadMessageType;
24+
import org.lfenergy.shapeshifter.api.model.UftpParticipantInformation;
2225
import org.lfenergy.shapeshifter.core.common.HttpStatusCode;
2326
import org.lfenergy.shapeshifter.core.model.SigningDetails;
2427
import org.lfenergy.shapeshifter.core.model.UftpMessage;
25-
import org.lfenergy.shapeshifter.core.model.UftpParticipant;
28+
import org.lfenergy.shapeshifter.core.service.ParticipantAuthorizationProvider;
2629
import org.lfenergy.shapeshifter.core.service.crypto.UftpCryptoService;
2730
import org.lfenergy.shapeshifter.core.service.participant.ParticipantResolutionService;
2831
import org.lfenergy.shapeshifter.core.service.serialization.UftpSerializer;
@@ -60,6 +63,7 @@ public class UftpSendMessageService {
6063
private final UftpSerializer serializer;
6164
private final UftpCryptoService cryptoService;
6265
private final ParticipantResolutionService participantService;
66+
private final ParticipantAuthorizationProvider participantAuthorizationProvider;
6367
private final UftpValidationService uftpValidationService;
6468
private final HttpClient httpClient;
6569

@@ -69,8 +73,9 @@ public class UftpSendMessageService {
6973
public UftpSendMessageService(@NonNull UftpSerializer serializer,
7074
@NonNull UftpCryptoService cryptoService,
7175
@NonNull ParticipantResolutionService participantService,
76+
@NonNull ParticipantAuthorizationProvider participantAuthorizationProvider,
7277
@NonNull UftpValidationService uftpValidationService) {
73-
this(serializer, cryptoService, participantService, uftpValidationService, HttpClient.newHttpClient());
78+
this(serializer, cryptoService, participantService, participantAuthorizationProvider, uftpValidationService, HttpClient.newHttpClient());
7479
}
7580

7681
/**
@@ -79,11 +84,13 @@ public UftpSendMessageService(@NonNull UftpSerializer serializer,
7984
public UftpSendMessageService(@NonNull UftpSerializer serializer,
8085
@NonNull UftpCryptoService cryptoService,
8186
@NonNull ParticipantResolutionService participantService,
87+
@NonNull ParticipantAuthorizationProvider participantAuthorizationProvider,
8288
@NonNull UftpValidationService uftpValidationService,
8389
@NonNull HttpClient httpClient) {
8490
this.serializer = serializer;
8591
this.cryptoService = cryptoService;
8692
this.participantService = participantService;
93+
this.participantAuthorizationProvider = participantAuthorizationProvider;
8794
this.uftpValidationService = uftpValidationService;
8895
this.httpClient = httpClient;
8996
}
@@ -116,7 +123,13 @@ public void attemptToValidateAndSendMessage(@NonNull PayloadMessageType payloadM
116123

117124
private void doSend(PayloadMessageType payloadMessage, SigningDetails details) {
118125
String signedXml = getSignedXml(payloadMessage, details);
119-
send(signedXml, details.recipient());
126+
UftpParticipantInformation participantInformation = participantService.getParticipantInformation(details.recipient());
127+
String url = participantInformation.endpoint();
128+
Map<String, String> additionalHeaders = new HashMap<>();
129+
if (participantInformation.requiresAuthorization()) {
130+
additionalHeaders.put("Authorization", participantAuthorizationProvider.getAuthorizationHeader(details.recipient()));
131+
}
132+
send(signedXml, url, additionalHeaders, MAX_FOLLOW_REDIRECTS);
120133
}
121134

122135
private String getSignedXml(PayloadMessageType payloadMessage, SigningDetails details) {
@@ -125,20 +138,18 @@ private String getSignedXml(PayloadMessageType payloadMessage, SigningDetails de
125138
return serializer.toXml(signedMessage);
126139
}
127140

128-
private void send(String signedXml, UftpParticipant recipient) {
129-
var url = participantService.getEndPointUrl(recipient);
130-
send(signedXml, url, MAX_FOLLOW_REDIRECTS);
131-
}
132-
133-
private void send(String signedXml, String url, int maxFollowRedirects) {
141+
private void send(String signedXml, String url, Map<String, String> additionalHeaders, int maxFollowRedirects) {
134142
try {
135143
log.debug(String.format("Sending message to: %s", url));
136144

137-
var request = HttpRequest.newBuilder()
145+
var requestBuilder = HttpRequest.newBuilder()
138146
.uri(new URI(url))
139147
.POST(BodyPublishers.ofString(signedXml))
140-
.setHeader("Content-Type", "text/xml")
141-
.build();
148+
.setHeader("Content-Type", "text/xml");
149+
for (var header : additionalHeaders.entrySet()) {
150+
requestBuilder.setHeader(header.getKey(), header.getValue());
151+
}
152+
var request = requestBuilder.build();
142153

143154
var response = httpClient.send(request, BodyHandlers.ofString());
144155

@@ -154,7 +165,7 @@ private void send(String signedXml, String url, int maxFollowRedirects) {
154165
var redirectUrl = response.headers().firstValue(REDIRECT_LOCATION_HEADER_NAME)
155166
.orElseThrow(() -> new UftpServerErrorException(MessageFormat.format(MSG_MISSING_REDIRECT_LOCATION, url), httpStatusCode));
156167

157-
send(signedXml, redirectUrl, maxFollowRedirects - 1);
168+
send(signedXml, redirectUrl, additionalHeaders, maxFollowRedirects - 1);
158169
} else if (httpStatusCode.isClientError()) {
159170
throw new UftpClientErrorException(MessageFormat.format(MSG_CLIENT_ERROR, response.statusCode(), url, response.body()), httpStatusCode);
160171
} else if (httpStatusCode.isServerError()) {

core/src/test/java/org/lfenergy/shapeshifter/core/common/xsd/XsdSchemaPoolTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ void create_throws() throws Exception {
8585

8686
verify(factoryPool).release(factory);
8787
}
88-
88+
8989
@Test
9090
void create_violate_xxe_then_fail() {
9191
doTestXXE(XXE_ATTACK, "DOCTYPE is disallowed when the feature \"http://apache.org/xml/features/disallow-doctype-decl\" set to true.");

0 commit comments

Comments
 (0)