Skip to content

Commit

Permalink
use STS instead of TokenGenerationService
Browse files Browse the repository at this point in the history
  • Loading branch information
paullatzelsperger committed Mar 14, 2024
1 parent eb29f17 commit 5b0c793
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 66 deletions.
2 changes: 1 addition & 1 deletion edc-extensions/tokenrefresh-handler/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ dependencies {
implementation(libs.edc.spi.http)
implementation(libs.edc.spi.token)
implementation(libs.edc.spi.jwt)
implementation(libs.edc.spi.identitytrust)
implementation(libs.edc.util)
implementation(libs.nimbus.jwt)
implementation(libs.edc.core.token) //needed for the JwtGenerationService


testImplementation(libs.edc.junit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,16 @@
package org.eclipse.tractusx.edc.common.tokenrefresh;

import org.eclipse.edc.edr.spi.store.EndpointDataReferenceStore;
import org.eclipse.edc.identitytrust.SecureTokenService;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
import org.eclipse.edc.spi.http.EdcHttpClient;
import org.eclipse.edc.spi.system.ServiceExtension;
import org.eclipse.edc.spi.system.ServiceExtensionContext;
import org.eclipse.edc.spi.types.TypeManager;
import org.eclipse.edc.token.JwtGenerationService;
import org.eclipse.edc.token.spi.TokenGenerationService;
import org.eclipse.tractusx.edc.spi.tokenrefresh.common.TokenRefreshHandler;

import java.security.PrivateKey;
import java.util.function.Supplier;

import static org.eclipse.tractusx.edc.common.tokenrefresh.TokenRefreshHandlerExtension.NAME;


Expand All @@ -47,7 +43,7 @@ public class TokenRefreshHandlerExtension implements ServiceExtension {
@Inject
private EdcHttpClient httpClient;
@Inject
private TokenGenerationService tokenGenerationService;
private SecureTokenService secureTokenService;
@Inject
private TypeManager typeManager;

Expand All @@ -58,20 +54,10 @@ public String name() {

@Provider
public TokenRefreshHandler createTokenRefreshHander(ServiceExtensionContext context) {
return new TokenRefreshHandlerImpl(edrStore, httpClient, getPrivateKeySupplier(context), getPublicKeySupplier(context), getOwnDid(context), context.getMonitor(), new JwtGenerationService(), typeManager.getMapper());
return new TokenRefreshHandlerImpl(edrStore, httpClient, getOwnDid(context), context.getMonitor(), secureTokenService, typeManager.getMapper());
}

private String getOwnDid(ServiceExtensionContext context) {
return context.getConfig().getString(PARTICIPANT_DID_PROPERTY);
}

private Supplier<String> getPublicKeySupplier(ServiceExtensionContext context) {
return null;
}

private Supplier<PrivateKey> getPrivateKeySupplier(ServiceExtensionContext context) {
return null;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -25,60 +25,55 @@
import okhttp3.Request;
import okhttp3.RequestBody;
import org.eclipse.edc.edr.spi.store.EndpointDataReferenceStore;
import org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames;
import org.eclipse.edc.identitytrust.SecureTokenService;
import org.eclipse.edc.spi.http.EdcHttpClient;
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.token.spi.KeyIdDecorator;
import org.eclipse.edc.token.spi.TokenDecorator;
import org.eclipse.edc.token.spi.TokenGenerationService;
import org.eclipse.edc.util.string.StringUtils;
import org.eclipse.tractusx.edc.spi.tokenrefresh.common.TokenRefreshHandler;
import org.eclipse.tractusx.edc.spi.tokenrefresh.dataplane.model.TokenResponse;

import java.io.IOException;
import java.security.PrivateKey;
import java.text.ParseException;
import java.util.List;
import java.util.function.Supplier;
import java.util.Map;

import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.AUDIENCE;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.ISSUER;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.JWT_ID;
import static org.eclipse.edc.jwt.spi.JwtRegisteredClaimNames.SUBJECT;
import static org.eclipse.edc.util.string.StringUtils.isNullOrBlank;

public class TokenRefreshHandlerImpl implements TokenRefreshHandler {
public static final String PROPERTY_AUTHORIZATION = "authorization";
public static final String PROPERTY_REFRESH_TOKEN = "refreshToken";
public static final String PROPERTY_REFRESH_ENDPOINT = "refreshEndpoint";
private final EndpointDataReferenceStore edrStore;
private final EdcHttpClient httpClient;
private final Supplier<PrivateKey> privateKeySupplier;
private final Supplier<String> publicKeySupplier;
private final String ownDid;
private final Monitor monitor;
private final TokenGenerationService tokenGenerationService;
private final SecureTokenService secureTokenService;
private final ObjectMapper objectMapper;

/**
* Creates a new TokenRefreshHandler
*
* @param edrStore a persistent storage where {@link org.eclipse.edc.spi.types.domain.edr.EndpointDataReference} objects are stored.
* @param httpClient needed to make the actual refresh call against the refresh endpoint
* @param privateKeySupplier a {@link Supplier} that provides the private key that is used to sign the authentication token
* @param publicKeySupplier provides the ID of the public key, which is added to the header of the authentication token.
* The public key material identified by this ID must be resolvable via the participant's DID document.
* @param ownDid the DID of this connector
* @param tokenGenerationService Service to generate the authentication token
* @param objectMapper ObjectMapper to interpret JSON responses
* @param edrStore a persistent storage where {@link org.eclipse.edc.spi.types.domain.edr.EndpointDataReference} objects are stored.
* @param httpClient needed to make the actual refresh call against the refresh endpoint
* @param ownDid the DID of this connector
* @param secureTokenService Service to generate the authentication token
* @param objectMapper ObjectMapper to interpret JSON responses
*/
public TokenRefreshHandlerImpl(EndpointDataReferenceStore edrStore,
EdcHttpClient httpClient,
Supplier<PrivateKey> privateKeySupplier,
Supplier<String> publicKeySupplier,
String ownDid,
Monitor monitor, TokenGenerationService tokenGenerationService, ObjectMapper objectMapper) {
Monitor monitor,
SecureTokenService secureTokenService,
ObjectMapper objectMapper) {
this.edrStore = edrStore;
this.httpClient = httpClient;
this.privateKeySupplier = privateKeySupplier;
this.publicKeySupplier = publicKeySupplier;
this.ownDid = ownDid;
this.monitor = monitor;
this.tokenGenerationService = tokenGenerationService;
this.secureTokenService = secureTokenService;
this.objectMapper = objectMapper;
}

Expand All @@ -89,9 +84,9 @@ public Result<TokenResponse> refreshToken(String tokenId) {
return Result.failure(edrResult.getFailureDetail());
}
var edr = edrResult.getContent();
var accessToken = edr.getStringProperty("authorization");
var refreshToken = edr.getProperties().get("refreshToken");
var refreshEndpoint = edr.getProperties().get("refreshEndpoint");
var accessToken = edr.getStringProperty(PROPERTY_AUTHORIZATION);
var refreshToken = edr.getProperties().get(PROPERTY_REFRESH_TOKEN);
var refreshEndpoint = edr.getProperties().get(PROPERTY_REFRESH_ENDPOINT);

if (isNullOrBlank(accessToken)) {
return Result.failure("Cannot perform token refresh: required property 'authorization' not found on EDR.");
Expand All @@ -103,20 +98,18 @@ public Result<TokenResponse> refreshToken(String tokenId) {
return Result.failure("Cannot perform token refresh: required property 'refreshEndpoint' not found on EDR.");
}

var audience = getStringClaim(accessToken, JwtRegisteredClaimNames.ISSUER);
var audience = getStringClaim(accessToken, ISSUER);
if (audience.failed()) {
return audience.mapTo();
}
var decorators = List.of(
new KeyIdDecorator(publicKeySupplier.get()),
tp -> tp.claims(JwtRegisteredClaimNames.JWT_ID, tokenId),
tp -> tp.claims(JwtRegisteredClaimNames.ISSUER, ownDid),
tp -> tp.claims(JwtRegisteredClaimNames.SUBJECT, ownDid),
tp -> tp.claims(JwtRegisteredClaimNames.AUDIENCE, audience.getContent()),
(TokenDecorator) tp -> tp.claims("access_token", accessToken)
var claims = Map.of(
JWT_ID, tokenId,
ISSUER, ownDid,
SUBJECT, ownDid,
AUDIENCE, audience.getContent()
);

var authTokenResult = tokenGenerationService.generate(privateKeySupplier, decorators.toArray(new TokenDecorator[0]));
var authTokenResult = secureTokenService.createToken(claims, accessToken);
if (authTokenResult.failed()) {
return authTokenResult.mapTo();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.eclipse.edc.edr.spi.store.EndpointDataReferenceStore;
import org.eclipse.edc.identitytrust.SecureTokenService;
import org.eclipse.edc.spi.http.EdcHttpClient;
import org.eclipse.edc.spi.iam.TokenRepresentation;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.result.StoreResult;
import org.eclipse.edc.spi.types.domain.DataAddress;
import org.eclipse.edc.token.spi.TokenDecorator;
import org.eclipse.edc.token.spi.TokenGenerationService;
import org.eclipse.tractusx.edc.spi.tokenrefresh.dataplane.model.TokenResponse;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach;
Expand All @@ -59,6 +58,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
Expand All @@ -74,7 +74,7 @@ class TokenRefreshHandlerImplTest {
private static final String PROVIDER_DID = "did:web:alice";
private final EndpointDataReferenceStore edrStore = mock();
private final EdcHttpClient mockedHttpClient = mock();
private final TokenGenerationService mockedTokenService = mock();
private final SecureTokenService mockedTokenService = mock();
private TokenRefreshHandlerImpl tokenRefreshHandler;
private PrivateKey consumerKey;
private ObjectMapper objectMapper;
Expand All @@ -94,14 +94,14 @@ private static String createJwt() {
void setup() throws JOSEException {
consumerKey = new ECKeyGenerator(Curve.P_256).generate().toPrivateKey();
objectMapper = new ObjectMapper();
tokenRefreshHandler = new TokenRefreshHandlerImpl(edrStore, mockedHttpClient, () -> consumerKey, () -> PUBLIC_KEY_ID, CONSUMER_DID, mock(),
tokenRefreshHandler = new TokenRefreshHandlerImpl(edrStore, mockedHttpClient, CONSUMER_DID, mock(),
mockedTokenService, objectMapper);
}

@Test
void refresh_validateCorrectRequest() throws IOException {
when(edrStore.resolveByTransferProcess(anyString())).thenReturn(StoreResult.success(createEdr().build()));
when(mockedTokenService.generate(any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build()));
when(mockedTokenService.createToken(anyMap(), anyString())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build()));
var tokenResponse = new TokenResponse("new-access-token", "new-refresh-token", 60 * 5L, "bearer");
var successResponse = createResponse(tokenResponse, 200, "");
when(mockedHttpClient.execute(any())).thenReturn(successResponse);
Expand Down Expand Up @@ -129,7 +129,7 @@ void refresh_edrNotFound() {

@ParameterizedTest(name = "{3}")
@ArgumentsSource(InvalidEdrProvider.class)
void refresh_edrLacksRequiredProperties(String authorization, String refreshToken, String refreshEndpoint, String desc) throws IOException {
void refresh_edrLacksRequiredProperties(String authorization, String refreshToken, String refreshEndpoint, String desc) {
var invalidEdr = DataAddress.Builder.newInstance().type("test-type")
.property("authorization", authorization)
.property("refreshToken", refreshToken)
Expand All @@ -145,7 +145,7 @@ void refresh_edrLacksRequiredProperties(String authorization, String refreshToke
@Test
void refresh_endpointReturnsFailure() throws IOException {
when(edrStore.resolveByTransferProcess(anyString())).thenReturn(StoreResult.success(createEdr().build()));
when(mockedTokenService.generate(any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build()));
when(mockedTokenService.createToken(anyMap(), anyString())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build()));
var response401 = createResponse(null, 401, "Not authorized");

when(mockedHttpClient.execute(any())).thenReturn(response401);
Expand All @@ -158,7 +158,7 @@ void refresh_endpointReturnsFailure() throws IOException {
@Test
void refresh_endpointReturnsEmptyBody() throws IOException {
when(edrStore.resolveByTransferProcess(anyString())).thenReturn(StoreResult.success(createEdr().build()));
when(mockedTokenService.generate(any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build()));
when(mockedTokenService.createToken(anyMap(), anyString())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build()));
var successResponse = createResponse(null, 200, "");
when(mockedHttpClient.execute(any())).thenReturn(successResponse);
var res = tokenRefreshHandler.refreshToken("token-id");
Expand All @@ -169,7 +169,7 @@ void refresh_endpointReturnsEmptyBody() throws IOException {
@Test
void refresh_ioException() throws IOException {
when(edrStore.resolveByTransferProcess(anyString())).thenReturn(StoreResult.success(createEdr().build()));
when(mockedTokenService.generate(any(), any(TokenDecorator[].class))).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build()));
when(mockedTokenService.createToken(anyMap(), anyString())).thenReturn(Result.success(TokenRepresentation.Builder.newInstance().token("foo-auth-token").build()));
when(mockedHttpClient.execute(any())).thenThrow(new IOException("test exception"));

assertThat(tokenRefreshHandler.refreshToken("token-id")).isFailed()
Expand All @@ -184,9 +184,9 @@ void refresh_accessTokenIsNotJwt() {
}

@Test
void refresh_tokenGenerationFailed() throws IOException {
void refresh_tokenGenerationFailed() {
when(edrStore.resolveByTransferProcess(anyString())).thenReturn(StoreResult.success(createEdr().build()));
when(mockedTokenService.generate(any(), any(TokenDecorator[].class))).thenReturn(Result.failure("foobar"));
when(mockedTokenService.createToken(anyMap(), anyString())).thenReturn(Result.failure("foobar"));
assertThat(tokenRefreshHandler.refreshToken("token-id")).isFailed()
.detail().isEqualTo("foobar");
}
Expand All @@ -213,7 +213,7 @@ private DataAddress.Builder createEdr() {

private static class InvalidEdrProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) throws Exception {
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
return Stream.of(
Arguments.of(createJwt(), "foo-refresh-token", null, "refresh endpoint is null"),
Arguments.of(createJwt(), "foo-refresh-token", "", "refresh endpoint is empty"),
Expand Down

0 comments on commit 5b0c793

Please sign in to comment.