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
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* For field injection, used in conjunction with {@link Inject} to specify the injection of the holder's did.
* For field injection, used in conjunction with {@link Inject} to specify the injection of a did.
*/
@Inherited
@Retention(RUNTIME)
@Target({ FIELD, PARAMETER })
public @interface HolderDid {
public @interface Did {
RoleType value();
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2025 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 org.eclipse.dataspacetck.dcp.system.annotation;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Tests that verify the issuance flow for a Credential Service implementation.
*/
@Inherited
@Retention(RUNTIME)
@Target({ METHOD, TYPE })
@Test
@Tag("IssuanceFlow")
public @interface IssuanceFlow {
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,24 @@

package org.eclipse.dataspacetck.dcp.system.annotation;

import org.eclipse.dataspacetck.core.api.system.Inject;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* For field injection, used in conjunction with {@link Inject} to specify the injection of the verifier's did.
* Tests that verify the issuance flow for an Issuer Service implementation.
*/
@Inherited
@Retention(RUNTIME)
@Target({ FIELD, PARAMETER })
public @interface VerifierDid {
@Target({ METHOD, TYPE })
@Test
@Tag("IssuerService")
public @interface IssuerService {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2025 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 org.eclipse.dataspacetck.dcp.system.annotation;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Tests that verify the presentation flow for a Credential Service implementation.
*/
@Inherited
@Retention(RUNTIME)
@Target({METHOD, TYPE})
@Test
@Tag("PresentationFlow")
public @interface PresentationFlow {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.eclipse.dataspacetck.dcp.system.annotation;

/**
* A holder, verifier or third-party.
*/
public enum RoleType {
HOLDER, VERIFIER, THIRD_PARTY
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* For field injection, used in conjunction with {@link Inject} to specify the injection of the issuer's did.
* For field injection, used in conjunction with {@link Inject} to specify the injection of a third-party service.
*/
@Inherited
@Retention(RUNTIME)
@Target({ FIELD, PARAMETER })
public @interface IssuerDid {
public @interface ThirdParty {
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public Map<String, Object> getExtensibleProperties() {
return extensibleProperties;
}

public Map<String, Object> toMap(){
public Map<String, Object> toMap() {
var map = new LinkedHashMap<String, Object>();
map.put(ID, id);
map.put(TYPE, List.of(type));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
import org.eclipse.dataspacetck.core.spi.system.SystemConfiguration;
import org.eclipse.dataspacetck.core.spi.system.SystemLauncher;
import org.eclipse.dataspacetck.dcp.system.annotation.AuthToken;
import org.eclipse.dataspacetck.dcp.system.annotation.HolderDid;
import org.eclipse.dataspacetck.dcp.system.annotation.Did;
import org.eclipse.dataspacetck.dcp.system.annotation.Holder;
import org.eclipse.dataspacetck.dcp.system.annotation.IssueCredentials;
import org.eclipse.dataspacetck.dcp.system.annotation.IssuerDid;
import org.eclipse.dataspacetck.dcp.system.annotation.ThirdParty;
import org.eclipse.dataspacetck.dcp.system.annotation.Verifier;
import org.eclipse.dataspacetck.dcp.system.annotation.VerifierDid;
import org.eclipse.dataspacetck.dcp.system.assembly.BaseAssembly;
import org.eclipse.dataspacetck.dcp.system.assembly.ServiceAssembly;
import org.eclipse.dataspacetck.dcp.system.crypto.KeyService;
Expand All @@ -34,6 +34,7 @@
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
Expand Down Expand Up @@ -65,21 +66,43 @@ public <T> T getService(Class<T> type, ServiceConfiguration configuration, Servi
if (type.isAssignableFrom(CredentialService.class)) {
return type.cast(assembly.getCredentialService());
} else if (type.isAssignableFrom(KeyService.class)) {
return hasAnnotation(Verifier.class, configuration) ? type.cast(baseAssembly.getVerifierKeyService())
: type.cast(baseAssembly.getHolderKeyService());
if (hasAnnotation(Verifier.class, configuration)) {
return type.cast(baseAssembly.getVerifierKeyService());
} else if (hasAnnotation(Holder.class, configuration)) {
return type.cast(baseAssembly.getHolderKeyService());
} else if (hasAnnotation(ThirdParty.class, configuration)) {
return type.cast(baseAssembly.getThirdPartyKeyService());
}
} else if (type.isAssignableFrom(DidService.class)) {
return hasAnnotation(Verifier.class, configuration) ? type.cast(baseAssembly.getVerifierDidService())
: type.cast(baseAssembly.getHolderDidService());
if (hasAnnotation(Verifier.class, configuration)) {
return type.cast(baseAssembly.getVerifierDidService());
} else if (hasAnnotation(Holder.class, configuration)) {
return type.cast(baseAssembly.getHolderDidService());
} else if (hasAnnotation(ThirdParty.class, configuration)) {
return type.cast(baseAssembly.getThirdPartyDidService());
}
} else if (type.isAssignableFrom(String.class)) {
if (hasAnnotation(VerifierDid.class, configuration)) {
return type.cast(baseAssembly.getVerifierDid());
} else if (hasAnnotation(HolderDid.class, configuration)) {
return type.cast(baseAssembly.getHolderDid());
} else if (hasAnnotation(IssuerDid.class, configuration)) {
return type.cast(baseAssembly.getIssuerDid());
} else if (hasAnnotation(AuthToken.class, configuration)) {
if (hasAnnotation(AuthToken.class, configuration)) {
return createAuthToken(type, configuration, assembly);
}

var did = getAnnotation(Did.class, configuration);
if (did.isPresent()) {
switch (did.get().value()) {
case HOLDER -> {
return type.cast(baseAssembly.getHolderDid());
}
case VERIFIER -> {
return type.cast(baseAssembly.getVerifierDid());
}
case THIRD_PARTY -> {
return type.cast(baseAssembly.getThirdPartyDid());
}
default -> {
throw new UnsupportedOperationException("Unsupported DID role: " + did.get().value());
}
}
}
}
return SystemLauncher.super.getService(type, configuration, resolver);
}
Expand All @@ -92,7 +115,7 @@ public void beforeExecution(ServiceConfiguration configuration, ServiceResolver
}

private <T> T createAuthToken(Class<T> type, ServiceConfiguration configuration, ServiceAssembly assembly) {
var scopes = Arrays.asList(getAnnotation(AuthToken.class, configuration).value());
var scopes = Arrays.asList(getAnnotation(AuthToken.class, configuration).orElseThrow().value());
var tokenResult = assembly.getStsClient().obtainReadToken(baseAssembly.getVerifierDid(), scopes);
if (tokenResult.failed()) {
throw new AssertionError(tokenResult.getFailure());
Expand All @@ -105,10 +128,9 @@ private boolean hasAnnotation(Class<? extends Annotation> annotation, ServiceCon
}

@SuppressWarnings({ "unchecked", "SameParameterValue" })
private <A extends Annotation> A getAnnotation(Class<A> annotation, ServiceConfiguration configuration) {
return (A) configuration.getAnnotations().stream()
private <A extends Annotation> Optional<A> getAnnotation(Class<A> annotation, ServiceConfiguration configuration) {
return (Optional<A>) configuration.getAnnotations().stream()
.filter(a -> a.annotationType().equals(annotation))
.findFirst()
.orElseThrow();
.findFirst();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public class BaseAssembly {
private DidService verifierDidService;
private TokenValidationService verifierTokenService;
private TokenValidationService holderTokenService;
private String thirdPartyDid;
private KeyServiceImpl thirdPartyKeyService;
private DidServiceImpl thirdPartyDidService;
private ObjectMapper mapper;

public ObjectMapper getMapper() {
Expand Down Expand Up @@ -100,11 +103,24 @@ public DidService getIssuerDidService() {
return issuerDidService;
}

public String getThirdPartyDid() {
return thirdPartyDid;
}

public KeyServiceImpl getThirdPartyKeyService() {
return thirdPartyKeyService;
}

public DidServiceImpl getThirdPartyDidService() {
return thirdPartyDidService;
}

public BaseAssembly(SystemConfiguration configuration) {
mapper = new ObjectMapper();
address = configuration.getPropertyAsString(TCK_CALLBACK_ADDRESS, TCK_DEFAULT_CALLBACK_ADDRESS);
verifierDid = parseDid("verifier");
issuerDid = parseDid("issuer");
thirdPartyDid = parseDid("thirdparty");
issuerKeyService = new KeyServiceImpl(Keys.generateEcKey());
issuerDidService = new DidServiceImpl(issuerDid, address, issuerKeyService);
holderDid = parseDid("holder");
Expand All @@ -114,6 +130,9 @@ public BaseAssembly(SystemConfiguration configuration) {
verifierTokenService = new TokenValidationServiceImpl(verifierDid);
verifierKeyService = new KeyServiceImpl(Keys.generateEcKey());
verifierDidService = new DidServiceImpl(verifierDid, address, verifierKeyService);

thirdPartyKeyService = new KeyServiceImpl(Keys.generateEcKey());
thirdPartyDidService = new DidServiceImpl(thirdPartyDid, address, thirdPartyKeyService);
}

private String parseDid(String discriminator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public ServiceAssembly(BaseAssembly baseAssembly, ServiceResolver resolver, Serv
endpoint.registerHandler("/holder/did.json", new DidDocumentHandler(baseAssembly.getHolderDidService(), mapper));
endpoint.registerHandler("/verifier/did.json", new DidDocumentHandler(baseAssembly.getVerifierDidService(), mapper));
endpoint.registerHandler("/issuer/did.json", new DidDocumentHandler(baseAssembly.getIssuerDidService(), mapper));
endpoint.registerHandler("/thirdparty/did.json", new DidDocumentHandler(baseAssembly.getThirdPartyDidService(), mapper));
}

public CredentialService getCredentialService() {
Expand Down Expand Up @@ -97,7 +98,7 @@ private void seedCredentials(BaseAssembly baseAssembly, SecureTokenServer secure
private VcContainer createVcContainer(String issuerDid, String holderDid,
JwtCredentialGenerator credentialGenerator,
String credentialType) {
var credential = createCredential(issuerDid, holderDid,credentialType);
var credential = createCredential(issuerDid, holderDid, credentialType);
var result = credentialGenerator.generateCredential(credential);
return new VcContainer(result.getContent(), credential, VC1_0_JWT);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public HandlerResponse apply(Map<String, List<String>> headers, InputStream body
if (!schemaResult.isEmpty()) {
var error = format("Schema validation failed: %s", schemaResult.stream().map(ValidationMessage::getMessage).collect(joining("\n")));
monitor.enableError().message(error).resetMode();
return new HandlerResponse(400, error);
return new HandlerResponse(400, error);
}

var issuer = jwt.getJWTClaimsSet().getIssuer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public Result<List<String>> validateWrite(String bearerDid, String correlationId

@NotNull
private String transformScopes(List<String> scopes) {
return scopes.stream().map(scope->{
return scopes.stream().map(scope -> {
if (!scope.startsWith(SCOPE_TYPE_ALIAS)) {
throw new IllegalArgumentException("Invalid scope: " + scope);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.eclipse.dataspacetck.dcp.system.service.Result;

import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static org.eclipse.dataspacetck.dcp.system.crypto.Keys.createVerifier;
import static org.eclipse.dataspacetck.dcp.system.service.Result.failure;
Expand All @@ -33,6 +35,7 @@
*/
public class TokenValidationServiceImpl implements TokenValidationService {
private final String audience;
private Map<String, String> usedJts = new ConcurrentHashMap<>();

public TokenValidationServiceImpl(String audience) {
this.audience = audience;
Expand All @@ -55,10 +58,16 @@ public Result<JWT> validateToken(String token) {
return failure("Issuer and subject do not match");
}

if (claims.getJWTID() == null) {
var jti = claims.getJWTID();
if (jti == null) {
return failure("JTI not specified");
}

if (usedJts.containsKey(jti)) {
return failure("JTI already used");
}
usedJts.put(jti, jti);

if (claims.getExpirationTime() == null) {
return failure("Expiration not specified");
}
Expand Down
Loading