Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build stage
FROM gradle:8.5-jdk21 AS build
FROM gradle:9.5-jdk21 AS build
WORKDIR /app

# Copy gradle files for dependency caching
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
#
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
package com.metaformsystems.redline.application.service;

public interface TokenProvider {
String getToken(String clientId, String clientSecret, String scopes);
String getToken(String participantContextId, String scopes);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2026 Metaform Systems, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package com.metaformsystems.redline.application.service;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;

import static java.util.Optional.ofNullable;

/**
* This provider reads a workload token from a file share, provided by Kubernetes, and exchanges it for a scoped token
* using the Token Exchange Protocol (RFC 8693 - OAuth 2.0 Token Exchange).
*/
@Component("token-exchange")
public class WorkloadTokenProvider implements TokenProvider {
private final String tokenFilePath;
private final String tokenExchangeAudience;
private final String defaultResource;
private final WebClient webClient;

public WorkloadTokenProvider(@Value("${token.file.path:/var/run/secrets/jwtlet/token}") String tokenFilePath,
@Value("${token.exchange.audience:edcv}") String tokenExchangeAudience,
@Value("${token.exchange.resource:redline}") String defaultResource,
@Qualifier("tokenExchangeClient") WebClient webClient) {
this.tokenFilePath = tokenFilePath;
this.tokenExchangeAudience = tokenExchangeAudience;
this.defaultResource = defaultResource;
this.webClient = webClient;
}

@Override
public String getToken(String resource, String scopes) {
try {
var tokenContent = Files.readString(Path.of(tokenFilePath));

var response = webClient.post()
.uri("/token")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")
.with("subject_token", tokenContent)
.with("audience", tokenExchangeAudience)
.with("resource", ofNullable(resource).orElse(defaultResource))
.with("scope", scopes))
.retrieve()
.bodyToMono(TokenResponse.class)
.block();

return Objects.requireNonNull(response).accessToken();

} catch (IOException e) {
throw new RuntimeException(e);
}
}

private record TokenResponse(@JsonProperty("access_token") String accessToken) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.metaformsystems.redline.domain.repository.ParticipantRepository;
import com.metaformsystems.redline.infrastructure.client.dataplane.dto.UploadResponse;
import com.metaformsystems.redline.infrastructure.client.management.dto.QuerySpec;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.buffer.DataBuffer;
Expand All @@ -42,7 +43,8 @@ public class DataPlaneApiClientImpl implements DataPlaneApiClient {
private final ParticipantRepository participantRepository;
private final TokenProvider tokenProvider;

public DataPlaneApiClientImpl(WebClient dataPlanePublicClient, WebClient dataPlaneInternalClient, ParticipantRepository participantRepository, TokenProvider tokenProvider) {
public DataPlaneApiClientImpl(WebClient dataPlanePublicClient, WebClient dataPlaneInternalClient, ParticipantRepository participantRepository,
@Qualifier("token-exchange") TokenProvider tokenProvider) {
this.dataPlanePublicClient = dataPlanePublicClient.mutate()
.exchangeStrategies(ExchangeStrategies.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(50 * 1024 * 1024))
Expand Down Expand Up @@ -118,9 +120,8 @@ public byte[] downloadFile(String authToken, String fileId) {
}

private String getToken(String participantContextId) {
var participantProfile = participantRepository.findByParticipantContextId(participantContextId)
participantRepository.findByParticipantContextId(participantContextId)
.orElseThrow(() -> new ObjectNotFoundException("Participant not found with context id: " + participantContextId));

return tokenProvider.getToken(participantProfile.getClientCredentials().clientId(), participantProfile.getClientCredentials().clientSecret(), "management-api:write management-api:read");
return tokenProvider.getToken(participantContextId, "read write");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,15 @@
*/
public interface IdentityHubClient {

// Participant Context operations
List<IdentityHubParticipantContext> getAllParticipants();

IdentityHubParticipantContext getParticipant(String participantContextId);

// Verifiable Credentials operations
List<VerifiableCredentialResource> getAllCredentials();

List<VerifiableCredentialResource> queryCredentialsByType(String participantContextId, String type);

VerifiableCredentialResource getCredentialRequest(String participantContextId, String holderPid);

void requestCredential(String participantContextId, CredentialRequestDto request);

// Key Pairs operations
List<KeyPairResource> getAllKeyPairs();

List<KeyPairResource> queryKeyPairByParticipantContextId(String participantContextId);

KeyPairResource getKeyPair(String participantContextId, String keyPairId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.metaformsystems.redline.infrastructure.client.identityhub.dto.KeyDescriptor;
import com.metaformsystems.redline.infrastructure.client.identityhub.dto.KeyPairResource;
import com.metaformsystems.redline.infrastructure.client.identityhub.dto.VerifiableCredentialResource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Component;
Expand All @@ -43,7 +44,7 @@ public class IdentityHubClientImpl implements IdentityHubClient {
private final ParticipantRepository participantRepository;

public IdentityHubClientImpl(WebClient identityHubWebClient,
TokenProvider tokenProvider,
@Qualifier("token-exchange") TokenProvider tokenProvider,
ParticipantRepository participantRepository,
@Value("${edc.api.clientId:provisioner}") String provisionerClientId,
@Value("${edc.api.clientsecret:provisioner-secret}") String provisionerClientSecret) {
Expand All @@ -54,19 +55,6 @@ public IdentityHubClientImpl(WebClient identityHubWebClient,
this.participantRepository = participantRepository;
}

@Override
public List<IdentityHubParticipantContext> getAllParticipants() {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path(IDENTITY_API_BASE + "/participants")
.build())
.header("Authorization", "Bearer " + getToken())
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<IdentityHubParticipantContext>>() {
})
.block();
}

@Override
public IdentityHubParticipantContext getParticipant(String participantContextId) {
return webClient.get()
Expand All @@ -77,19 +65,6 @@ public IdentityHubParticipantContext getParticipant(String participantContextId)
.block();
}

@Override
public List<VerifiableCredentialResource> getAllCredentials() {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path(IDENTITY_API_BASE + "/credentials")
.build())
.header("Authorization", "Bearer " + getToken())
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<VerifiableCredentialResource>>() {
})
.block();
}

@Override
public List<VerifiableCredentialResource> queryCredentialsByType(String participantContextId, String type) {
return webClient.get()
Expand Down Expand Up @@ -129,19 +104,6 @@ public void requestCredential(String participantContextId, CredentialRequestDto
.block();
}

@Override
public List<KeyPairResource> getAllKeyPairs() {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path(IDENTITY_API_BASE + "/keypairs")
.build())
.header("Authorization", "Bearer " + getToken())
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<KeyPairResource>>() {
})
.block();
}

@Override
public List<KeyPairResource> queryKeyPairByParticipantContextId(String participantContextId) {
return webClient.get()
Expand Down Expand Up @@ -221,15 +183,10 @@ public void getDidState(String participantContextId, DidRequestPayload payload)
.block();
}

private String getToken() {
return tokenProvider.getToken(provisionerClientId, provisionerClientSecret, "identity-api:read");
}

private String getToken(String participantContextId) {
var participantProfile = participantRepository.findByParticipantContextId(participantContextId)
participantRepository.findByParticipantContextId(participantContextId)
.orElseThrow(() -> new ObjectNotFoundException("Participant not found with context id: " + participantContextId));

var token = tokenProvider.getToken(participantProfile.getClientCredentials().clientId(), participantProfile.getClientCredentials().clientSecret(), "identity-api:write identity-api:read");
return token;
return tokenProvider.getToken(participantContextId, "read write");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.metaformsystems.redline.infrastructure.client.management.dto.TransferRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
Expand All @@ -57,7 +58,7 @@ public class ManagementApiClientImpl implements ManagementApiClient {
private final ClientCredentials provisionerCredentials;

public ManagementApiClientImpl(WebClient controlPlaneWebClient,
TokenProvider tokenProvider,
@Qualifier("token-exchange") TokenProvider tokenProvider,
ParticipantRepository participantRepository,
ObjectMapper objectMapper,
@Value("${edc.api.clientId:provisioner}") String adminClientId,
Expand Down Expand Up @@ -215,7 +216,7 @@ public List<Map<String, Object>> queryContractNegotiations(String participantCon
@Override
public void createCelExpression(CelExpression celExpression) {

var token = tokenProvider.getToken(provisionerCredentials.clientId(), provisionerCredentials.clientSecret(), "management-api:write management-api:read");
var token = tokenProvider.getToken(null, "management-api:write management-api:read");
controlPlaneWebClient.post()
.uri("/v5beta/celexpressions")
.header("Authorization", "Bearer %s".formatted(token))
Expand Down Expand Up @@ -320,10 +321,10 @@ public ContractAgreement getAgreement(String participantContextId, String negoti
}

private String getToken(String participantContextId) {
var participantProfile = participantRepository.findByParticipantContextId(participantContextId)
participantRepository.findByParticipantContextId(participantContextId)
.orElseThrow(() -> new ObjectNotFoundException("Participant not found with context id: " + participantContextId));

return tokenProvider.getToken(participantProfile.getClientCredentials().clientId(), participantProfile.getClientCredentials().clientSecret(), "management-api:write management-api:read");
return tokenProvider.getToken(participantContextId, "read write");
}

}
Loading
Loading