Skip to content

Commit

Permalink
feat: make CredentialService URL configurable (#3635)
Browse files Browse the repository at this point in the history
  • Loading branch information
paullatzelsperger authored Nov 21, 2023
1 parent c609610 commit 36ab86d
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,52 @@

package org.eclipse.edc.iam.identitytrust.core;

import jakarta.json.Json;
import org.eclipse.edc.iam.identitytrust.core.defaults.DefaultCredentialServiceClient;
import org.eclipse.edc.iam.identitytrust.core.defaults.DefaultTrustedIssuerRegistry;
import org.eclipse.edc.iam.identitytrust.core.defaults.InMemorySignatureSuiteRegistry;
import org.eclipse.edc.iam.identitytrust.core.scope.IatpScopeExtractorRegistry;
import org.eclipse.edc.iam.identitytrust.sts.embedded.EmbeddedSecureTokenService;
import org.eclipse.edc.identitytrust.CredentialServiceClient;
import org.eclipse.edc.identitytrust.SecureTokenService;
import org.eclipse.edc.identitytrust.TrustedIssuerRegistry;
import org.eclipse.edc.identitytrust.scope.ScopeExtractorRegistry;
import org.eclipse.edc.identitytrust.verification.SignatureSuiteRegistry;
import org.eclipse.edc.jsonld.spi.JsonLd;
import org.eclipse.edc.jwt.TokenGenerationServiceImpl;
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.runtime.metamodel.annotation.Setting;
import org.eclipse.edc.spi.EdcException;
import org.eclipse.edc.spi.http.EdcHttpClient;
import org.eclipse.edc.spi.security.KeyPairFactory;
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.transform.spi.TypeTransformerRegistry;

import java.security.KeyPair;
import java.time.Clock;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static org.eclipse.edc.spi.CoreConstants.JSON_LD;

@Extension("Identity And Trust Extension to register default services")
public class IatpDefaultServicesExtension implements ServiceExtension {

@Setting(value = "Alias of private key used for signing tokens, retrieved from private key resolver", defaultValue = "A random EC private key")
public static final String STS_PRIVATE_KEY_ALIAS = "edc.iam.sts.privatekey.alias";
@Setting(value = "Alias of public key used for verifying the tokens, retrieved from the vault", defaultValue = "A random EC public key")
public static final String STS_PUBLIC_KEY_ALIAS = "edc.iam.sts.publickey.alias";
@Setting(value = "URL of the CredentialService used to present credentials", required = true)
public static final String CREDENTIALSERVICE_URL_PROPERTY = "edc.iam.credentialservice.url";
// not a setting, it's defined in Oauth2ServiceExtension
private static final String OAUTH_TOKENURL_PROPERTY = "edc.oauth.token.url";
@Setting(value = "Self-issued ID Token expiration in minutes. By default is 5 minutes", defaultValue = "" + IatpDefaultServicesExtension.DEFAULT_STS_TOKEN_EXPIRATION_MIN)
private static final String STS_TOKEN_EXPIRATION = "edc.iam.sts.token.expiration"; // in minutes

private static final int DEFAULT_STS_TOKEN_EXPIRATION_MIN = 5;

@Inject
Expand All @@ -57,6 +68,18 @@ public class IatpDefaultServicesExtension implements ServiceExtension {
@Inject
private Clock clock;

@Inject
private TypeTransformerRegistry typeTransformerRegistry;

@Inject
private EdcHttpClient httpClient;

@Inject
private TypeManager typeManager;

@Inject
private JsonLd jsonLd;

@Provider(isDefault = true)
public SecureTokenService createDefaultTokenService(ServiceExtensionContext context) {
context.getMonitor().info("Using the Embedded STS client, as no other implementation was provided.");
Expand Down Expand Up @@ -87,6 +110,13 @@ public ScopeExtractorRegistry scopeExtractorRegistry() {
return new IatpScopeExtractorRegistry();
}

@Provider(isDefault = true)
public CredentialServiceClient createClient(ServiceExtensionContext context) {
return new DefaultCredentialServiceClient(httpClient, Json.createBuilderFactory(Map.of()),
typeManager.getMapper(JSON_LD), typeTransformerRegistry, jsonLd, context.getMonitor(),
context.getConfig().getString(CREDENTIALSERVICE_URL_PROPERTY));
}

private KeyPair keyPairFromConfig(ServiceExtensionContext context) {
var pubKeyAlias = context.getSetting(STS_PUBLIC_KEY_ALIAS, null);
var privKeyAlias = context.getSetting(STS_PRIVATE_KEY_ALIAS, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@

package org.eclipse.edc.iam.identitytrust.core;

import jakarta.json.Json;
import org.eclipse.edc.iam.did.spi.resolution.DidResolverRegistry;
import org.eclipse.edc.iam.identitytrust.IdentityAndTrustService;
import org.eclipse.edc.iam.identitytrust.core.defaults.DefaultCredentialServiceClient;
import org.eclipse.edc.iam.identitytrust.validation.SelfIssuedIdTokenValidator;
import org.eclipse.edc.iam.identitytrust.verification.MultiFormatPresentationVerifier;
import org.eclipse.edc.identitytrust.CredentialServiceClient;
Expand All @@ -32,18 +30,15 @@
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
import org.eclipse.edc.runtime.metamodel.annotation.Provider;
import org.eclipse.edc.runtime.metamodel.annotation.Setting;
import org.eclipse.edc.spi.http.EdcHttpClient;
import org.eclipse.edc.spi.iam.IdentityService;
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.transform.spi.TypeTransformerRegistry;
import org.eclipse.edc.verifiablecredentials.jwt.JwtPresentationVerifier;
import org.eclipse.edc.verifiablecredentials.linkeddata.LdpVerifier;
import org.eclipse.edc.verification.jwt.SelfIssuedIdTokenVerifier;

import java.time.Clock;
import java.util.Map;

import static org.eclipse.edc.spi.CoreConstants.JSON_LD;

Expand All @@ -53,6 +48,7 @@ public class IdentityAndTrustExtension implements ServiceExtension {
@Setting(value = "DID of this connector", required = true)
public static final String ISSUER_DID_PROPERTY = "edc.iam.issuer.id";


@Inject
private SecureTokenService secureTokenService;

Expand All @@ -78,11 +74,6 @@ public class IdentityAndTrustExtension implements ServiceExtension {
@Inject
private Clock clock;

@Inject
private TypeTransformerRegistry typeTransformerRegistry;

@Inject
private EdcHttpClient httpClient;

private JwtValidator jwtValidator;
private JwtVerifier jwtVerifier;
Expand Down Expand Up @@ -127,11 +118,6 @@ public JwtVerifier getJwtVerifier() {
return jwtVerifier;
}

@Provider
public CredentialServiceClient createClient(ServiceExtensionContext context) {
return new DefaultCredentialServiceClient(httpClient, Json.createBuilderFactory(Map.of()),
typeManager.getMapper(JSON_LD), typeTransformerRegistry, jsonLd, context.getMonitor());
}

private String getOwnDid(ServiceExtensionContext context) {
// todo: this must be config value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,20 @@ public class DefaultCredentialServiceClient implements CredentialServiceClient {
private final TypeTransformerRegistry transformerRegistry;
private final JsonLd jsonLd;
private final Monitor monitor;
private final String credentialServiceUrl;

public DefaultCredentialServiceClient(EdcHttpClient httpClient, JsonBuilderFactory jsonFactory, ObjectMapper jsonLdMapper, TypeTransformerRegistry transformerRegistry, JsonLd jsonLd, Monitor monitor) {
public DefaultCredentialServiceClient(EdcHttpClient httpClient, JsonBuilderFactory jsonFactory, ObjectMapper jsonLdMapper, TypeTransformerRegistry transformerRegistry, JsonLd jsonLd, Monitor monitor, String credentialServiceUrl) {
this.httpClient = httpClient;
this.jsonFactory = jsonFactory;
this.objectMapper = jsonLdMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
this.transformerRegistry = transformerRegistry;
this.jsonLd = jsonLd;
this.monitor = monitor;
this.credentialServiceUrl = credentialServiceUrl;
}

@Override
public Result<List<VerifiablePresentationContainer>> requestPresentation(String credentialServiceUrl, String selfIssuedTokenJwt, List<String> scopes) {
public Result<List<VerifiablePresentationContainer>> requestPresentation(String selfIssuedTokenJwt, List<String> scopes) {
var query = createPresentationQuery(scopes);

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ void setup() {
var jsonLdMock = mock(JsonLd.class);
when(jsonLdMock.expand(any())).thenAnswer(a -> success(a.getArgument(0)));
client = new DefaultCredentialServiceClient(httpClientMock, Json.createBuilderFactory(Map.of()),
createObjectMapper(), registry, jsonLdMock, mock());
createObjectMapper(), registry, jsonLdMock, mock(), CS_URL);
}

@Test
Expand All @@ -70,7 +70,7 @@ void requestPresentation_singleLdpVp() throws IOException {
when(httpClientMock.execute(any()))
.thenReturn(response(200, getResourceFileContentAsString("single_ldp-vp.json")));

var result = client.requestPresentation(CS_URL, "foo", List.of());
var result = client.requestPresentation("foo", List.of());
assertThat(result.succeeded()).isTrue();
assertThat(result.getContent()).hasSize(1).allMatch(vpc -> vpc.format() == CredentialFormat.JSON_LD);
}
Expand All @@ -81,7 +81,7 @@ void requestPresentation_singleJwtVp() throws IOException {
when(httpClientMock.execute(any()))
.thenReturn(response(200, getResourceFileContentAsString("single_jwt-vp.json")));

var result = client.requestPresentation(CS_URL, "foo", List.of());
var result = client.requestPresentation("foo", List.of());
assertThat(result.succeeded()).isTrue();
assertThat(result.getContent()).hasSize(1).allMatch(vpc -> vpc.format() == CredentialFormat.JWT);
}
Expand All @@ -92,7 +92,7 @@ void requestPresentationLdp_multipleVp_mixed() throws IOException {
when(httpClientMock.execute(any()))
.thenReturn(response(200, getResourceFileContentAsString("multiple_vp-token_mixed.json")));

var result = client.requestPresentation(CS_URL, "foo", List.of());
var result = client.requestPresentation("foo", List.of());
assertThat(result.succeeded()).isTrue();
assertThat(result.getContent()).hasSize(2)
.anySatisfy(vp -> assertThat(vp.format()).isEqualTo(CredentialFormat.JSON_LD))
Expand All @@ -105,7 +105,7 @@ void requestPresentation_mulipleVp_onlyLdp() throws IOException {
when(httpClientMock.execute(any()))
.thenReturn(response(200, getResourceFileContentAsString("multiple_vp-token_ldp.json")));

var result = client.requestPresentation(CS_URL, "foo", List.of());
var result = client.requestPresentation("foo", List.of());
assertThat(result.succeeded()).isTrue();
assertThat(result.getContent()).hasSize(2)
.allSatisfy(vp -> assertThat(vp.format()).isEqualTo(CredentialFormat.JSON_LD));
Expand All @@ -117,7 +117,7 @@ void requestPresentation_mulipleVp_onlyJwt() throws IOException {
when(httpClientMock.execute(any()))
.thenReturn(response(200, getResourceFileContentAsString("multiple_vp-token_jwt.json")));

var result = client.requestPresentation(CS_URL, "foo", List.of());
var result = client.requestPresentation("foo", List.of());
assertThat(result.succeeded()).isTrue();
assertThat(result.getContent()).hasSize(2)
.allSatisfy(vp -> assertThat(vp.format()).isEqualTo(CredentialFormat.JWT));
Expand All @@ -129,7 +129,7 @@ void requestPresentation_csReturnsError(int httpCode) throws IOException {
when(httpClientMock.execute(any()))
.thenReturn(response(httpCode, "Test failure"));

var res = client.requestPresentation(CS_URL, "foo", List.of());
var res = client.requestPresentation("foo", List.of());
assertThat(res.failed()).isTrue();
assertThat(res.getFailureDetail()).isEqualTo("Presentation Query failed: HTTP %s, message: Test failure".formatted(httpCode));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ public Result<ClaimToken> verifyJwtToken(TokenRepresentation tokenRepresentation
return issuerResult.mapTo();
}

var vpResponse = credentialServiceClient.requestPresentation(null, null, null);
// todo: create SI Token, extract scope strings
var vpResponse = credentialServiceClient.requestPresentation(null, null);

if (vpResponse.failed()) {
return vpResponse.mapTo();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,19 +135,19 @@ class VerifyJwtToken {

@Test
void presentationRequestFails() {
when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(failure("test-failure"));
when(mockedClient.requestPresentation(any(), any())).thenReturn(failure("test-failure"));
var token = createJwt();
var result = service.verifyJwtToken(token, "test-audience");
assertThat(result).isFailed().detail().isEqualTo("test-failure");
verifyNoInteractions(mockedVerifier);
verify(mockedClient).requestPresentation(any(), any(), any());
verify(mockedClient).requestPresentation(any(), any());

}

@Test
void cryptographicError() {
when(mockedVerifier.verifyPresentation(any())).thenReturn(Result.failure("Cryptographic error"));
when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(createPresentationContainer())));
when(mockedClient.requestPresentation(any(), any())).thenReturn(success(List.of(createPresentationContainer())));
var token = createJwt();
var result = service.verifyJwtToken(token, "test-audience");
assertThat(result).isFailed().detail().isEqualTo("Cryptographic error");
Expand All @@ -163,7 +163,7 @@ void notYetValid() {
.build();
var vpContainer = new VerifiablePresentationContainer("test-vp", CredentialFormat.JSON_LD, presentation);
when(mockedVerifier.verifyPresentation(any())).thenReturn(success());
when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(vpContainer)));
when(mockedClient.requestPresentation(any(), any())).thenReturn(success(List.of(vpContainer)));
var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID);
var result = service.verifyJwtToken(token, "test-audience");
assertThat(result).isFailed().messages()
Expand All @@ -184,7 +184,7 @@ void oneInvalidSubjectId() {
.build();
var vpContainer = new VerifiablePresentationContainer("test-vp", CredentialFormat.JSON_LD, presentation);
when(mockedVerifier.verifyPresentation(any())).thenReturn(success());
when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(vpContainer)));
when(mockedClient.requestPresentation(any(), any())).thenReturn(success(List.of(vpContainer)));
var token = createJwt(CONSUMER_DID, EXPECTED_OWN_DID);
var result = service.verifyJwtToken(token, "test-audience");
assertThat(result).isFailed().messages()
Expand All @@ -209,7 +209,7 @@ void credentialHasInvalidIssuer_issuerIsUrl() {
.build();
var vpContainer = new VerifiablePresentationContainer("test-vp", CredentialFormat.JSON_LD, presentation);
when(mockedVerifier.verifyPresentation(any())).thenReturn(success());
when(mockedClient.requestPresentation(any(), any(), any())).thenReturn(success(List.of(vpContainer)));
when(mockedClient.requestPresentation(any(), any())).thenReturn(success(List.of(vpContainer)));
var token = createJwt(consumerDid, EXPECTED_OWN_DID);
var result = service.verifyJwtToken(token, "test-audience");
assertThat(result).isFailed().messages()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,11 @@ public interface CredentialServiceClient {
* <p>
* Note that sending a DIF Presentation Definition is not supported yet and will result in a 5xx error.
*
* @param csUrl The URL of the CredentialService, from which the presentation is to be requested.
* @param siTokenJwt A Self-Issued ID token in JWT format, that contains the access_token
* @param scopes A list of strings, each containing a <a href="https://github.com/eclipse-tractusx/identity-trust/blob/main/specifications/M1/verifiable.presentation.protocol.md#31-access-scopes">scope definition</a>
* @return A list of {@link VerifiablePresentationContainer} objects, or a failure if the request was unsuccessful.
*/
Result<List<VerifiablePresentationContainer>> requestPresentation(String csUrl, String siTokenJwt, List<String> scopes);
Result<List<VerifiablePresentationContainer>> requestPresentation(String siTokenJwt, List<String> scopes);

//todo: add write api?
}

0 comments on commit 36ab86d

Please sign in to comment.