From e3c5450fe2e0084f9deee16dff59e228afa40966 Mon Sep 17 00:00:00 2001 From: Dominik Pinsel Date: Fri, 12 Jul 2024 14:34:25 +0200 Subject: [PATCH] feat(identity-trust)!: update IATP protocol BREAKING CHANGE: `/api/presentations/iatp` endpoint now accepts PresentationQueryMessage and returns PresentationResponseMessage objects. Signed-off-by: Dominik Pinsel --- .../security/PresentationIatpFilter.java | 5 +- .../config/security/SecurityConfig.java | 5 +- .../constant/RestURI.java | 6 + .../controller/PresentationController.java | 38 ++++- .../dto/PresentationResponseMessage.java | 58 +++++++ .../reader/TractusXJsonLdReader.java | 79 +++++++++ .../TractusXPresentationRequestReader.java | 85 +++++++++ .../utils/ResourceUtil.java | 61 +++++++ .../resources/jsonld/IdentityMinusTrust.json | 127 ++++++++++++++ ...n.presentation-exchange.submission.v1.json | 15 ++ ...PresentationResponseSerializationTest.java | 112 ++++++++++++ .../identityminustrust/TokenRequestTest.java | 161 ++++++++++++++++++ .../reader/PresentationRequestReaderTest.java | 49 ++++++ .../messages/presentation_query.json | 10 ++ 14 files changed, 798 insertions(+), 13 deletions(-) create mode 100644 miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseMessage.java create mode 100644 miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXJsonLdReader.java create mode 100644 miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXPresentationRequestReader.java create mode 100644 miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/ResourceUtil.java create mode 100644 miw/src/main/resources/jsonld/IdentityMinusTrust.json create mode 100644 miw/src/main/resources/jsonld/identity.foundation.presentation-exchange.submission.v1.json create mode 100644 miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseSerializationTest.java create mode 100644 miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/identityminustrust/TokenRequestTest.java create mode 100644 miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/reader/PresentationRequestReaderTest.java create mode 100644 miw/src/test/resources/identityminustrust/messages/presentation_query.json diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java index 61ca5acd0..cefefb8fb 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java @@ -43,7 +43,8 @@ public class PresentationIatpFilter extends GenericFilterBean { - RequestMatcher customFilterUrl = new AntPathRequestMatcher(RestURI.API_PRESENTATIONS_IATP); + RequestMatcher customFilterUrl1 = new AntPathRequestMatcher(RestURI.API_PRESENTATIONS_IATP); + RequestMatcher customFilterUrl2 = new AntPathRequestMatcher(RestURI.API_PRESENTATIONS_IATP_WORKAROUND); STSTokenValidationService validationService; @@ -57,7 +58,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; - if (customFilterUrl.matches(httpServletRequest)) { + if (customFilterUrl1.matches(httpServletRequest) || customFilterUrl2.matches(httpServletRequest)) { String authHeader = httpServletRequest.getHeader("Authorization"); if (StringUtils.isEmpty(authHeader)) { httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java index 59bce9fad..148f18759 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java @@ -81,7 +81,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(new AntPathRequestMatcher("/ui/swagger-ui/**")).permitAll() .requestMatchers(new AntPathRequestMatcher("/actuator/health/**")).permitAll() .requestMatchers(new AntPathRequestMatcher("/api/token", POST.name())).permitAll() - .requestMatchers(new AntPathRequestMatcher("/api/presentations/iatp", GET.name())).permitAll() + .requestMatchers(new AntPathRequestMatcher(RestURI.API_PRESENTATIONS_IATP, POST.name())).permitAll() + .requestMatchers(new AntPathRequestMatcher(RestURI.API_PRESENTATIONS_IATP_WORKAROUND, POST.name())).permitAll() .requestMatchers(new AntPathRequestMatcher("/actuator/loggers/**")).hasRole(ApplicationRole.ROLE_MANAGE_APP) //did document resolve APIs @@ -137,7 +138,7 @@ public WebSecurityCustomizer securityCustomizer() { */ @Bean public AuthenticationEventPublisher authenticationEventPublisher - (ApplicationEventPublisher applicationEventPublisher) { + (ApplicationEventPublisher applicationEventPublisher) { return new DefaultAuthenticationEventPublisher(applicationEventPublisher); } } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/RestURI.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/RestURI.java index 764a0af4d..97e73c975 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/RestURI.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/RestURI.java @@ -80,4 +80,10 @@ private RestURI() { */ public static final String API_PRESENTATIONS_IATP = "/api/presentations/iatp"; + /** + * The constant API_PRESENTATIONS_IATP_WORKAROUND. THe EDC assumes (hard coded) that the presentation query endpoint is at /presentations/query. + * To mitigate this issue the MIW has to provide the same endpoint (without documentation), besides the correct one. + */ + public static final String API_PRESENTATIONS_IATP_WORKAROUND = "/presentations/query"; + } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java index 1a2f7d878..3f5b5a0c0 100644 --- a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java @@ -24,23 +24,28 @@ import com.nimbusds.jwt.SignedJWT; import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.managedidentitywallets.apidocs.PresentationControllerApiDocs.GetVerifiablePresentationIATPApiDocs; import org.eclipse.tractusx.managedidentitywallets.apidocs.PresentationControllerApiDocs.PostVerifiablePresentationApiDocs; import org.eclipse.tractusx.managedidentitywallets.apidocs.PresentationControllerApiDocs.PostVerifiablePresentationValidationApiDocs; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; +import org.eclipse.tractusx.managedidentitywallets.dto.PresentationResponseMessage; +import org.eclipse.tractusx.managedidentitywallets.reader.TractusXPresentationRequestReader; import org.eclipse.tractusx.managedidentitywallets.service.PresentationService; +import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentation; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.io.InputStream; import java.security.Principal; +import java.util.List; import java.util.Map; import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getAccessToken; @@ -55,6 +60,8 @@ public class PresentationController extends BaseController { private final PresentationService presentationService; + private final TractusXPresentationRequestReader presentationRequestReader; + /** * Create presentation response entity. * @@ -97,17 +104,30 @@ public ResponseEntity> validatePresentation(@RequestBody Map /** * Create presentation response entity for VC types provided in STS token. * - * @param stsToken the STS token with required scopes - * @param asJwt as JWT VP response + * @param stsToken the STS token with required scopes + * @param asJwt as JWT VP response * @return the VP response entity */ - @GetMapping(path = RestURI.API_PRESENTATIONS_IATP, produces = { MediaType.APPLICATION_JSON_VALUE }) + + @PostMapping(path = { RestURI.API_PRESENTATIONS_IATP, RestURI.API_PRESENTATIONS_IATP_WORKAROUND }, produces = { MediaType.APPLICATION_JSON_VALUE }) @GetVerifiablePresentationIATPApiDocs - public ResponseEntity> createPresentation(@Parameter(hidden = true) @RequestHeader(name = "Authorization") String stsToken, - @RequestParam(name = "asJwt", required = false, defaultValue = "false") boolean asJwt) { - SignedJWT accessToken = getAccessToken(stsToken); - Map vp = presentationService.createVpWithRequiredScopes(accessToken, asJwt); - return ResponseEntity.ok(vp); + @SneakyThrows + public ResponseEntity createPresentation(@Parameter(hidden = true) @RequestHeader(name = "Authorization") String stsToken, + @RequestParam(name = "asJwt", required = false, defaultValue = "false") boolean asJwt, + InputStream is) { + try { + + final List requestedScopes = presentationRequestReader.readVerifiableCredentialScopes(is); + // requested scopes are ignored until the documentation is better refined + + SignedJWT accessToken = getAccessToken(stsToken); + Map map = presentationService.createVpWithRequiredScopes(accessToken, asJwt); + VerifiablePresentation verifiablePresentation = new VerifiablePresentation((Map)map.get("vp")); + PresentationResponseMessage message = new PresentationResponseMessage(verifiablePresentation); + return ResponseEntity.ok(message); + } catch (TractusXPresentationRequestReader.InvalidPresentationQueryMessageResource e) { + return ResponseEntity.badRequest().build(); + } } } diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseMessage.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseMessage.java new file mode 100644 index 000000000..5636f3cfc --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseMessage.java @@ -0,0 +1,58 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ****************************************************************************** + */ + +package org.eclipse.tractusx.managedidentitywallets.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentation; + +import java.util.List; + +/** + * Class to represent the response message of a presentation request. + * Defined in JsonLD Tractus-X context.json. + *

+ * As `presentationSubmission` a not well-defined, we will just skip the property for HTTP responses. Defining all types as 'Json' make the whole idea of using Json-Linked-Data a waste of time, but ok. + *

+ * The `presentation` property is only specified as 'Json'. For this implementation we will assume these are Presentations from ether the Verifiable Credential Data Model v1.1 or Verifiable Credential Data Model v2.0. + */ +@Getter +public class PresentationResponseMessage { + + + public PresentationResponseMessage(VerifiablePresentation verifiablePresentation) { + this(List.of(verifiablePresentation)); + } + + public PresentationResponseMessage(List verifiablePresentations) { + this.verifiablePresentations = verifiablePresentations; + } + + @JsonProperty("@context") + private List contexts = List.of("https://w3id.org/tractusx-trust/v0.8"); + + @JsonProperty("@type") + private List types = List.of("PresentationResponseMessage"); + + @JsonProperty("presentation") + private List verifiablePresentations; +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXJsonLdReader.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXJsonLdReader.java new file mode 100644 index 000000000..075c046ab --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXJsonLdReader.java @@ -0,0 +1,79 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ****************************************************************************** + */ + +package org.eclipse.tractusx.managedidentitywallets.reader; + +import com.apicatalog.jsonld.JsonLdError; +import com.apicatalog.jsonld.JsonLdOptions; +import com.apicatalog.jsonld.document.JsonDocument; +import com.apicatalog.jsonld.processor.ExpansionProcessor; +import jakarta.json.JsonArray; +import lombok.NonNull; +import org.eclipse.tractusx.managedidentitywallets.utils.ResourceUtil; +import org.eclipse.tractusx.ssi.lib.model.RemoteDocumentLoader; + +import java.io.InputStream; +import java.net.URI; +import java.util.Map; + +public class TractusXJsonLdReader { + + private static final String TRACTUS_X_CONTEXT_RESOURCE = "jsonld/IdentityMinusTrust.json"; + private static final URI TRACTUS_X_CONTEXT = URI.create("https://w3id.org/tractusx-trust/v0.8"); + private static final URI IDENTITY_FOUNDATION_CREDENTIAL_SUBMISSION_CONTEXT = URI.create("https://identity.foundation/presentation-exchange/submission/v1"); + private static final String IDENTITY_FOUNDATION_CREDENTIAL_SUBMISSION_RESOURCE = "jsonld/identity.foundation.presentation-exchange.submission.v1.json"; + + + private final RemoteDocumentLoader documentLoader = RemoteDocumentLoader.DOCUMENT_LOADER; + + public TractusXJsonLdReader() { + + documentLoader.setEnableLocalCache(true); + + if (!documentLoader.getLocalCache().containsKey(TRACTUS_X_CONTEXT)) { + cacheOfflineResource(TRACTUS_X_CONTEXT_RESOURCE, TRACTUS_X_CONTEXT); + } + if (!documentLoader.getLocalCache().containsKey(IDENTITY_FOUNDATION_CREDENTIAL_SUBMISSION_CONTEXT)) { + cacheOfflineResource(IDENTITY_FOUNDATION_CREDENTIAL_SUBMISSION_RESOURCE, IDENTITY_FOUNDATION_CREDENTIAL_SUBMISSION_CONTEXT); + } + } + + public JsonArray expand(@NonNull final InputStream documentStream) throws JsonLdError { + + final JsonLdOptions jsonLdOptions = new JsonLdOptions(); + jsonLdOptions.setDocumentLoader(documentLoader); + + final JsonDocument document = JsonDocument.of(com.apicatalog.jsonld.http.media.MediaType.JSON_LD, documentStream); + return ExpansionProcessor.expand(document, jsonLdOptions, false); + } + + private void cacheOfflineResource(final String resource, final URI context) { + try { + final InputStream resourceStream = ResourceUtil.getResourceStream(resource); + final JsonDocument identityMinusTrustDocument; + identityMinusTrustDocument = JsonDocument.of(com.apicatalog.jsonld.http.media.MediaType.JSON_LD, resourceStream); + documentLoader.getLocalCache().put(context, identityMinusTrustDocument); + } catch (JsonLdError e) { + // If this ever fails, it is a programming error. Loading of the embedded context resource is checked by Unit Tests. + throw new RuntimeException("Could not parse Tractus-X JsonL-d context from resource. This should never happen. Resource: '%s'".formatted(resource), e); + } + } +} diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXPresentationRequestReader.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXPresentationRequestReader.java new file mode 100644 index 000000000..502bdeb3b --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/reader/TractusXPresentationRequestReader.java @@ -0,0 +1,85 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ****************************************************************************** + */ + +package org.eclipse.tractusx.managedidentitywallets.reader; + +import com.apicatalog.jsonld.JsonLdError; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonString; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.InputStream; +import java.util.List; + +@Slf4j +@Component +public class TractusXPresentationRequestReader extends TractusXJsonLdReader { + + private static final String JSON_LD_TYPE = "@type"; + private static final String JSON_LD_VALUE = "@value"; + private static final String TRACTUS_X_PRESENTATION_QUERY_MESSAGE_TYPE = "https://w3id.org/tractusx-trust/v0.8/PresentationQueryMessage"; + private static final String TRACTUS_X_SCOPE_TYPE = "https://w3id.org/tractusx-trust/v0.8/scope"; + + public List readVerifiableCredentialScopes(InputStream is) throws InvalidPresentationQueryMessageResource { + try { + + final JsonArray jsonArray = expand(is); + + if (jsonArray.size() != 1) { + log.atDebug().addArgument(jsonArray::toString).log("Expanded JSON-LD: {}"); + throw new InvalidPresentationQueryMessageResource("Expected a single JSON object. Found %d".formatted(jsonArray.size())); + } + + var jsonObject = jsonArray.getJsonObject(0); + + final JsonArray typeArray = jsonObject.getJsonArray(JSON_LD_TYPE); + final List types = typeArray.getValuesAs(JsonString.class).stream().map(JsonString::getString).toList(); + if (!types.contains(TRACTUS_X_PRESENTATION_QUERY_MESSAGE_TYPE)) { + log.atDebug().addArgument(jsonArray::toString).log("Expanded JSON-LD: {}"); + throw new InvalidPresentationQueryMessageResource("Unexpected type. Expected %s".formatted(TRACTUS_X_PRESENTATION_QUERY_MESSAGE_TYPE)); + } + + final JsonArray scopes = jsonObject.getJsonArray(TRACTUS_X_SCOPE_TYPE); + return scopes.getValuesAs(JsonObject.class) + .stream() + .map(o -> o.getJsonString(JSON_LD_VALUE)) + .map(JsonString::getString) + .toList(); + + } catch (JsonLdError e) { + throw new InvalidPresentationQueryMessageResource(e); + } + } + + public static class InvalidPresentationQueryMessageResource extends Exception { + public InvalidPresentationQueryMessageResource(String message) { + super(message); + } + + public InvalidPresentationQueryMessageResource(Throwable cause) { + super(cause); + } + } + +} + diff --git a/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/ResourceUtil.java b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/ResourceUtil.java new file mode 100644 index 000000000..6bbd6fd81 --- /dev/null +++ b/miw/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/ResourceUtil.java @@ -0,0 +1,61 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ****************************************************************************** + */ + +package org.eclipse.tractusx.managedidentitywallets.utils; + +import lombok.SneakyThrows; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; + +public final class ResourceUtil { + + @SneakyThrows + public static InputStream getResourceStream(String resourceName) { + var stream= ResourceUtil.class.getClassLoader().getResourceAsStream(resourceName); + if(stream == null) { + throw new IllegalArgumentException("File not found"); + } + + return stream; + } + + @SneakyThrows + public static String loadResource(String resourceName) { + StringBuilder content = new StringBuilder(); + + // load resource and return it + try (final InputStream is = ResourceUtil.class.getClassLoader().getResourceAsStream(resourceName)) { + if (is == null) { + throw new IllegalArgumentException("File not found"); + } + + final BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + String line; + while ((line = reader.readLine()) != null) { + content.append(line).append(System.lineSeparator()); + } + } + + return content.toString(); + } +} diff --git a/miw/src/main/resources/jsonld/IdentityMinusTrust.json b/miw/src/main/resources/jsonld/IdentityMinusTrust.json new file mode 100644 index 000000000..4a1fa6fbb --- /dev/null +++ b/miw/src/main/resources/jsonld/IdentityMinusTrust.json @@ -0,0 +1,127 @@ +{ + "@context" : { + "@version" : 1.1, + "@protected" : true, + "iatp" : "https://w3id.org/tractusx-trust/v0.8/", + "cred" : "https://www.w3.org/2018/credentials/", + "xsd" : "http://www.w3.org/2001/XMLSchema/", + "CredentialContainer" : { + "@id" : "iatp:CredentialContainer", + "@context" : { + "payload" : { + "@id" : "iatp:payload", + "@type" : "xsd:string" + } + } + }, + "CredentialMessage" : { + "@id" : "iatp:CredentialMessage", + "@context" : { + "credentials" : "iatp:credentials" + } + }, + "CredentialObject" : { + "@id" : "iatp:CredentialObject", + "@context" : { + "credentialType" : { + "@id" : "iatp:credentialType", + "@container" : "@set" + }, + "format" : "iatp:format", + "offerReason" : { + "@id" : "iatp:offerReason", + "@type" : "xsd:string" + }, + "bindingMethods" : { + "@id" : "iatp:bindingMethods", + "@type" : "xsd:string", + "@container" : "@set" + }, + "cryptographicSuites" : { + "@id" : "iatp:cryptographicSuites", + "@type" : "xsd:string", + "@container" : "@set" + }, + "issuancePolicy" : "iatp:issuancePolicy" + } + }, + "CredentialOfferMessage" : { + "@id" : "iatp:CredentialOfferMessage", + "@context" : { + "credentialIssuer" : "cred:issuer", + "credentials" : "iatp:credentials" + } + }, + "CredentialRequestMessage" : { + "@id" : "iatp:CredentialRequestMessage", + "@context" : { + "format" : "iatp:format", + "type" : "@type" + } + }, + "CredentialService" : "iatp:CredentialService", + "CredentialStatus" : { + "@id" : "iatp:CredentialStatus", + "@context" : { + "requestId" : { + "@id" : "iatp:requestId", + "@type" : "@id" + }, + "status" : { + "@id" : "iatp:status", + "@type" : "xsd:string" + } + } + }, + "IssuerMetadata" : { + "@id" : "iatp:IssuerMetadata", + "@context" : { + "credentialIssuer" : "cred:issuer", + "credentialsSupported" : { + "@id" : "iatp:credentialsSupported", + "@container" : "@set" + } + } + }, + "PresentationQueryMessage" : { + "@id" : "iatp:PresentationQueryMessage", + "@context" : { + "presentationDefinition" : { + "@id" : "iatp:presentationDefinition", + "@type" : "@json" + }, + "scope" : { + "@id" : "iatp:scope", + "@type" : "xsd:string", + "@container" : "@set" + } + } + }, + "PresentationResponseMessage" : { + "@id" : "iatp:PresentationResponseMessage", + "@context" : { + "presentation" : { + "@id" : "iatp:presentation", + "@type" : "@json" + }, + "presentationSubmission" : { + "@id" : "iatp:presentationSubmission", + "@type" : "@json" + } + } + }, + "credentials" : { + "@id" : "iatp:credentials", + "@container" : "@set" + }, + "credentialSubject" : { + "@id" : "iatp:credentialSubject", + "@type" : "cred:credentialSubject" + }, + "format" : { + "@id" : "iatp:format", + "@type" : "xsd:string" + }, + "type" : "@type" + } +} diff --git a/miw/src/main/resources/jsonld/identity.foundation.presentation-exchange.submission.v1.json b/miw/src/main/resources/jsonld/identity.foundation.presentation-exchange.submission.v1.json new file mode 100644 index 000000000..49653cebb --- /dev/null +++ b/miw/src/main/resources/jsonld/identity.foundation.presentation-exchange.submission.v1.json @@ -0,0 +1,15 @@ +{ + "@context": { + "@version": 1.1, + "PresentationSubmission": { + "@id": "https://identity.foundation/presentation-exchange/#presentation-submission", + "@context": { + "@version": 1.1, + "presentation_submission": { + "@id": "https://identity.foundation/presentation-exchange/#presentation-submission", + "@type": "@json" + } + } + } + } +} diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseSerializationTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseSerializationTest.java new file mode 100644 index 000000000..43dc24fec --- /dev/null +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/dto/PresentationResponseSerializationTest.java @@ -0,0 +1,112 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ****************************************************************************** + */ + +package org.eclipse.tractusx.managedidentitywallets.dto; + +import com.github.tomakehurst.wiremock.common.StreamSources; +import lombok.SneakyThrows; +import org.eclipse.tractusx.managedidentitywallets.reader.TractusXJsonLdReader; +import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentation; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +/** + * This test verifies that the serialized output of the Presentation Response DTO is JsonLD and Tractus-X compliant. + *

+ * It does so by comparing the serialized output with a predefined expected output. Like a contract test. + *

+ */ +public class PresentationResponseSerializationTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + /* Please note: The order of the properties is important, as the Unit Tests does some String comparison. */ + final String Presentation = """ + { + "id": "urn:uuid:3978344f-8596-4c3a-a978-8fcaba3903c5", + "type": ["VerifiablePresentation", "ExamplePresentation"], + "@context": [ + "https://www.w3.org/ns/credentials/v2" + ], + "verifiableCredential": [{ + "@context": "https://www.w3.org/ns/credentials/v2", + "id": "data:application/vc+sd-jwt;QzVjV...RMjU", + "issuer": "did:example:123", + "issuanceDate": "2020-03-10T04:24:12.164Z", + "type": "VerifiableCredential", + "credentialSubject": { + "id": "did:example:456", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science and Arts" + } + } + }] + }"""; + + /* Please note: The order of the properties is important, as the Unit Tests does some String comparison. */ + final String ExpectedPresentationResponse = "{\n" + + " \"@context\" : [\n" + + " \"https://w3id.org/tractusx-trust/v0.8\"\n" + + " ],\n" + + " \"@type\" : [\n" + + " \"PresentationResponseMessage\"\n" + + " ],\n" + + " \"presentation\" : [\n" + + Presentation + + " ]\n" + + " }\n" + + " ]\n" + + "}\n"; + + @Test + @SneakyThrows + public void testPresentationResponseSerialization() { + var presentation = getPresentation(); + + var response = new PresentationResponseMessage(presentation); + + var serialized = MAPPER.writeValueAsString(response); + + + var serializedDocument = new StreamSources.StringInputStreamSource(serialized, StandardCharsets.UTF_8).getStream(); + var expectedDocument = new StreamSources.StringInputStreamSource(ExpectedPresentationResponse, StandardCharsets.UTF_8).getStream(); + + var reader = new TractusXJsonLdReader(); + var normalizedSerializedDocument = reader.expand(serializedDocument).toString(); + var normalizedExpectedDocument = reader.expand(expectedDocument).toString(); + + + var isEqual = normalizedSerializedDocument.equals(normalizedExpectedDocument); + + Assertions.assertTrue(isEqual, "Expected both documents to be equal.\n%s\n%s".formatted(normalizedSerializedDocument, normalizedExpectedDocument)); + } + + @SneakyThrows + private VerifiablePresentation getPresentation() { + var map = MAPPER.readValue(Presentation, Map.class); + return new VerifiablePresentation(map); + } +} diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/identityminustrust/TokenRequestTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/identityminustrust/TokenRequestTest.java new file mode 100644 index 000000000..44317ee08 --- /dev/null +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/identityminustrust/TokenRequestTest.java @@ -0,0 +1,161 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ****************************************************************************** + */ + +package org.eclipse.tractusx.managedidentitywallets.identityminustrust; + +import lombok.SneakyThrows; +import org.eclipse.tractusx.managedidentitywallets.ManagedIdentityWalletsApplication; +import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings; +import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer; +import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; +import org.eclipse.tractusx.managedidentitywallets.service.IssuersCredentialService; +import org.eclipse.tractusx.managedidentitywallets.utils.AuthenticationUtils; +import org.eclipse.tractusx.managedidentitywallets.utils.ResourceUtil; +import org.eclipse.tractusx.managedidentitywallets.utils.TestUtils; +import org.eclipse.tractusx.ssi.lib.did.web.DidWebFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.List; +import java.util.Map; + +import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; + + +@DirtiesContext +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class }) +@ContextConfiguration(initializers = { TestContextInitializer.class }) +public class TokenRequestTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private static final String PRESENTATION_QUERY_REQUEST = "identityminustrust/messages/presentation_query.json"; + + @Autowired + private MIWSettings miwSettings; + + @Autowired + private TestRestTemplate restTemplate; + + @Autowired + private TestRestTemplate testTemplate; + + @Autowired + private IssuersCredentialService issuersCredentialService; + + private String bpn; + + private String clientId; + + private String clientSecret; + + @BeforeEach + @SneakyThrows + public void initWallets() { + // given + bpn = TestUtils.getRandomBpmNumber(); + String partnerBpn = TestUtils.getRandomBpmNumber(); + clientId = bpn; + clientSecret = bpn; + AuthenticationUtils.setupKeycloakClient(clientId, clientSecret, bpn); + AuthenticationUtils.setupKeycloakClient("partner", "partner", partnerBpn); + String did = DidWebFactory.fromHostnameAndPath(miwSettings.host(), bpn).toString(); + String didPartner = DidWebFactory.fromHostnameAndPath(miwSettings.host(), partnerBpn).toString(); + String defaultLocation = miwSettings.host() + COLON_SEPARATOR + bpn; + TestUtils.createWallet(bpn, did, testTemplate, miwSettings.authorityWalletBpn(), defaultLocation); + String defaultLocationPartner = miwSettings.host() + COLON_SEPARATOR + partnerBpn; + TestUtils.createWallet(partnerBpn, didPartner, testTemplate, miwSettings.authorityWalletBpn(), defaultLocationPartner); + + var vc = "{\n" + + " \"id\": \"did:web:foo#f255c392-82aa-483a-90a3-3c8697cd246a\",\n" + + " \"@context\": [\n" + + " \"https://www.w3.org/2018/credentials/v1\",\n" + + " \"https://w3id.org/security/suites/jws-2020/v1\"\n" + + " ],\n" + + " \"type\": [\"VerifiableCredential\", \"MembershipCredential\"],\n" + + " \"issuanceDate\": \"2021-06-16T18:56:59Z\",\n" + + " \"expirationDate\": \"2022-06-16T18:56:59Z\",\n" + + " \"issuer\": \"" + miwSettings.authorityWalletDid() + "\",\n" + + " \"credentialSubject\": {\n" + + " \"type\":\"MembershipCredential\",\n" + + " \"holderIdentifier\": \"" + did + "\",\n" + + " \"memberOf\":\"Catena-X\",\n" + + " \"status\":\"Active\",\n" + + " \"startTime656\":\"2021-06-16T18:56:59Z\"\n" + + " }\n" + + "}"; + + issuersCredentialService.issueCredentialUsingBaseWallet( + did, + MAPPER.readValue(vc, Map.class), + false, + miwSettings.authorityWalletBpn() + ); + } + + @Test + @SneakyThrows + public void testPresentationQueryWithToken() { + // when + String body = "audience=%s&client_id=%s&client_secret=%s&grant_type=client_credentials&bearer_access_scope=org.eclipse.tractusx.vc.type:MembershipCredential:read"; + String requestBody = String.format(body, bpn, clientId, clientSecret); + // then + HttpHeaders headers = new HttpHeaders(); + headers.put(HttpHeaders.CONTENT_TYPE, List.of(MediaType.APPLICATION_FORM_URLENCODED_VALUE)); + HttpEntity entity = new HttpEntity<>(requestBody, headers); + ResponseEntity> response = testTemplate.exchange( + "/api/token", + HttpMethod.POST, + entity, + new ParameterizedTypeReference<>() { + } + ); + + var jwt = (String) response.getBody().get("access_token"); + + final String message2 = ResourceUtil.loadResource(PRESENTATION_QUERY_REQUEST); + final Map data2 = MAPPER.readValue(message2, Map.class); + + final HttpHeaders headers2 = new HttpHeaders(); + headers2.set(HttpHeaders.AUTHORIZATION, jwt); + final HttpEntity> entity2 = new HttpEntity<>(data2, headers2); + var result2 = restTemplate + .postForEntity(RestURI.API_PRESENTATIONS_IATP, entity2, String.class); + + System.out.println("RESULT:\n" + result2.toString()); + + Assertions.assertTrue(result2.getStatusCode().is2xxSuccessful()); + } + +} diff --git a/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/reader/PresentationRequestReaderTest.java b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/reader/PresentationRequestReaderTest.java new file mode 100644 index 000000000..75ec3fd59 --- /dev/null +++ b/miw/src/test/java/org/eclipse/tractusx/managedidentitywallets/reader/PresentationRequestReaderTest.java @@ -0,0 +1,49 @@ +/* + * ******************************************************************************* + * Copyright (c) 2021,2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ****************************************************************************** + */ + +package org.eclipse.tractusx.managedidentitywallets.reader; + +import lombok.SneakyThrows; +import org.eclipse.tractusx.managedidentitywallets.utils.ResourceUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; +import java.util.List; + +public class PresentationRequestReaderTest { + + private final TractusXPresentationRequestReader presentationRequestReader = new TractusXPresentationRequestReader(); + + @Test + @SneakyThrows + public void readCredentialsTest() { + + final InputStream is = ResourceUtil.getResourceStream("identityminustrust/messages/presentation_query.json"); + + final List credentialScopes = presentationRequestReader.readVerifiableCredentialScopes(is); + + final String expected = "org.eclipse.tractusx.vc.type:MembershipCredential:read"; + + System.out.printf("Found credentials: %s", credentialScopes.toString()); + Assertions.assertTrue(credentialScopes.contains(expected), "Expected %s".formatted(expected)); + } +} diff --git a/miw/src/test/resources/identityminustrust/messages/presentation_query.json b/miw/src/test/resources/identityminustrust/messages/presentation_query.json new file mode 100644 index 000000000..5b0cd0345 --- /dev/null +++ b/miw/src/test/resources/identityminustrust/messages/presentation_query.json @@ -0,0 +1,10 @@ +{ + "scope" : [ + "org.eclipse.tractusx.vc.type:MembershipCredential:read" + ], + "@context" : [ + "https://identity.foundation/presentation-exchange/submission/v1", + "https://w3id.org/tractusx-trust/v0.8" + ], + "@type" : "PresentationQueryMessage" +}