From 4feebd40dabe45e94f73cafcec410c2e51016758 Mon Sep 17 00:00:00 2001 From: aleksandra-bel Date: Wed, 21 Feb 2024 17:07:47 +0100 Subject: [PATCH 01/13] feat: add service method, controller, config for scope matching --- .../security/PresentationIatpFilter.java | 78 ++++++++++++++++++ .../config/security/SecurityConfig.java | 10 +++ .../constant/RestURI.java | 1 + .../controller/PresentationController.java | 22 ++++- .../exception/MissingVcTypesException.java | 29 +++++++ .../PermissionViolationException.java | 29 +++++++ .../service/PresentationService.java | 69 ++++++++++++++-- .../utils/TokenParsingUtils.java | 81 +++++++++++++++++++ 8 files changed, 310 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java create mode 100644 src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/MissingVcTypesException.java create mode 100644 src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/PermissionViolationException.java create mode 100644 src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java new file mode 100644 index 000000000..dac29fcb3 --- /dev/null +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java @@ -0,0 +1,78 @@ +/* + * ******************************************************************************* + * 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.config.security; + +import io.micrometer.common.util.StringUtils; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; +import org.eclipse.tractusx.managedidentitywallets.constant.TokenValidationErrors; +import org.eclipse.tractusx.managedidentitywallets.dto.ValidationResult; +import org.eclipse.tractusx.managedidentitywallets.service.STSTokenValidationService; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.web.filter.GenericFilterBean; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +public class PresentationIatpFilter extends GenericFilterBean { + + RequestMatcher customFilterUrl = new AntPathRequestMatcher(RestURI.API_PRESENTATIONS_IATP); + + STSTokenValidationService validationService; + + public PresentationIatpFilter(STSTokenValidationService validationService) { + this.validationService = validationService; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + + + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + + if (customFilterUrl.matches(httpServletRequest)) { + String authHeader = httpServletRequest.getHeader("Authorization"); + if (StringUtils.isEmpty(authHeader)) { + httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } else { + ValidationResult result = validationService.validateToken(authHeader); + if (!result.isValid()) { + List errors = result.getErrors(); + String content = Arrays.toString(errors.toArray()); + httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + httpServletResponse.setContentLength(content.length()); + httpServletResponse.getWriter().write(content); + } else { + chain.doFilter(request, response); + } + } + } + } +} diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java index b74541da4..6d81af27a 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java @@ -25,6 +25,8 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.managedidentitywallets.constant.ApplicationRole; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; +import org.eclipse.tractusx.managedidentitywallets.service.STSTokenValidationService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -36,6 +38,7 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import static org.springframework.http.HttpMethod.GET; @@ -51,6 +54,9 @@ @AllArgsConstructor public class SecurityConfig { + @Autowired + private final STSTokenValidationService validationService; + private final SecurityConfigProperties securityConfigProperties; /** @@ -74,6 +80,7 @@ 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("/actuator/loggers/**")).hasRole(ApplicationRole.ROLE_MANAGE_APP) //did document resolve APIs @@ -110,6 +117,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(new AntPathRequestMatcher("/error")).permitAll() ).oauth2ResourceServer(resourceServer -> resourceServer.jwt(jwt -> jwt.jwtAuthenticationConverter(new CustomAuthenticationConverter(securityConfigProperties.clientId())))); + + http.addFilterAfter(new PresentationIatpFilter(validationService), BasicAuthenticationFilter.class); + return http.build(); } diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/RestURI.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/RestURI.java index a52149feb..db5415337 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/RestURI.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/RestURI.java @@ -77,5 +77,6 @@ private RestURI() { public static final String API_PRESENTATIONS = "/api/presentations"; public static final String API_PRESENTATIONS_VALIDATION = "/api/presentations/validation"; + public static final String API_PRESENTATIONS_IATP = "/api/presentations/iatp"; } diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java index fe052f5cf..d41eae0fd 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java @@ -21,10 +21,11 @@ package org.eclipse.tractusx.managedidentitywallets.controller; +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.PostVerifiablePresentationApiDocs; import org.eclipse.tractusx.managedidentitywallets.apidocs.PresentationControllerApiDocs.PostVerifiablePresentationValidationApiDocs; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; @@ -32,14 +33,18 @@ 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.security.Principal; import java.util.Map; +import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getAccessToken; + /** * The type Presentation controller. */ @@ -90,4 +95,19 @@ public ResponseEntity> validatePresentation(@RequestBody Map log.debug("Received request to validate presentation"); return ResponseEntity.status(HttpStatus.OK).body(presentationService.validatePresentation(data, asJwt, withCredentialExpiryDate, audience)); } + + /** + * Create presentation response entity for VC types provided in STS token. + * + * @param stsToken the STS token with required scopes + * @return the VP response entity + */ + @SneakyThrows + @GetMapping(path = RestURI.API_PRESENTATIONS_IATP, produces = { MediaType.APPLICATION_JSON_VALUE }) + // @SecureTokenControllerApiDoc.PostSecureTokenDoc TODO create API docs + public ResponseEntity> createPresentation(@RequestHeader(name = "Authorization") String stsToken) { + SignedJWT accessToken = getAccessToken(stsToken); + Map vp = presentationService.createVpWithRequiredScopes(accessToken); + return ResponseEntity.ok(vp); + } } diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/MissingVcTypesException.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/MissingVcTypesException.java new file mode 100644 index 000000000..c7994ffb2 --- /dev/null +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/MissingVcTypesException.java @@ -0,0 +1,29 @@ +/* + * ******************************************************************************* + * 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.exception; + +public class MissingVcTypesException extends RuntimeException { + + public MissingVcTypesException(String message) { + super(message); + } +} diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/PermissionViolationException.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/PermissionViolationException.java new file mode 100644 index 000000000..ae868e007 --- /dev/null +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/PermissionViolationException.java @@ -0,0 +1,29 @@ +/* + * ******************************************************************************* + * 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.exception; + +public class PermissionViolationException extends RuntimeException { + + public PermissionViolationException(String message) { + super(message); + } +} diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java index 75739bdaf..fa99929ae 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java @@ -21,13 +21,12 @@ package org.eclipse.tractusx.managedidentitywallets.service; -import com.ctc.wstx.shaded.msv_core.util.Uri; import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import com.smartsensesolutions.java.commons.base.repository.BaseRepository; import com.smartsensesolutions.java.commons.base.service.BaseService; import com.smartsensesolutions.java.commons.specification.SpecificationUtil; -import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -37,27 +36,25 @@ import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; import org.eclipse.tractusx.managedidentitywallets.dao.repository.HoldersCredentialRepository; import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; +import org.eclipse.tractusx.managedidentitywallets.exception.MissingVcTypesException; +import org.eclipse.tractusx.managedidentitywallets.exception.PermissionViolationException; import org.eclipse.tractusx.managedidentitywallets.utils.Validate; import org.eclipse.tractusx.ssi.lib.crypt.octet.OctetKeyPairFactory; import org.eclipse.tractusx.ssi.lib.crypt.x21559.x21559PrivateKey; -import org.eclipse.tractusx.ssi.lib.did.resolver.DidDocumentResolver; import org.eclipse.tractusx.ssi.lib.did.resolver.DidResolver; import org.eclipse.tractusx.ssi.lib.exception.InvalidJsonLdException; import org.eclipse.tractusx.ssi.lib.exception.InvalidePrivateKeyFormat; import org.eclipse.tractusx.ssi.lib.exception.JwtExpiredException; -import org.eclipse.tractusx.ssi.lib.exception.UnsupportedSignatureTypeException; import org.eclipse.tractusx.ssi.lib.jwt.SignedJwtFactory; import org.eclipse.tractusx.ssi.lib.jwt.SignedJwtValidator; import org.eclipse.tractusx.ssi.lib.jwt.SignedJwtVerifier; import org.eclipse.tractusx.ssi.lib.model.did.Did; -import org.eclipse.tractusx.ssi.lib.model.did.DidDocument; import org.eclipse.tractusx.ssi.lib.model.did.DidParser; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentation; import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentationBuilder; import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentationType; import org.eclipse.tractusx.ssi.lib.proof.LinkedDataProofValidation; -import org.eclipse.tractusx.ssi.lib.proof.SignatureType; import org.eclipse.tractusx.ssi.lib.serialization.jsonLd.JsonLdSerializerImpl; import org.eclipse.tractusx.ssi.lib.serialization.jwt.SerializedJwtPresentationFactory; import org.eclipse.tractusx.ssi.lib.serialization.jwt.SerializedJwtPresentationFactoryImpl; @@ -66,7 +63,14 @@ import org.springframework.util.StringUtils; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getClaimsSet; +import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getScope; /** * The type Presentation service. @@ -108,7 +112,6 @@ protected SpecificationUtil getSpecificationUtil() { * @param callerBpn the caller bpn * @return the map */ - @SneakyThrows({InvalidePrivateKeyFormat.class}) public Map createPresentation(Map data, boolean asJwt, String audience, String callerBpn) { List> verifiableCredentialList = (List>) data.get(StringPool.VERIFIABLE_CREDENTIALS); @@ -121,6 +124,11 @@ public Map createPresentation(Map data, boolean verifiableCredentials.add(verifiableCredential); }); + return buildVP(asJwt, audience, callerBpn, callerWallet, verifiableCredentials); + } + + @SneakyThrows({ InvalidePrivateKeyFormat.class }) + private Map buildVP(boolean asJwt, String audience, String callerBpn, Wallet callerWallet, List verifiableCredentials) { Map response = new HashMap<>(); if (asJwt) { log.debug("Creating VP as JWT for bpn ->{}", callerBpn); @@ -276,4 +284,49 @@ private boolean validateCredential(VerifiableCredential credential) { } return isValid; } + + public Map createVpWithRequiredScopes(SignedJWT innerJWT) { + List holdersCredentials = new ArrayList<>(); + List missingVCTypes = new ArrayList<>(); + List verifiableCredentials = new ArrayList<>(); + + JWTClaimsSet jwtClaimsSet = getClaimsSet(innerJWT); + String scopeValue = getScope(jwtClaimsSet); + String[] scopes = scopeValue.split(" "); + + for (String scope : scopes) { + String[] scopeParts = scope.split(":"); + String vcType = scopeParts[1]; + checkReadPermission(scopeParts[2]); + + List credentials = + holdersCredentialRepository.getByHolderDidAndType(jwtClaimsSet.getIssuer(), vcType); + if ((null == credentials) || credentials.isEmpty()) { + missingVCTypes.add(String.format("%s MISSING", vcType)); + } else { + holdersCredentials.addAll(credentials); + } + } + + checkMissingVcs(missingVCTypes); + + Wallet callerWallet = commonService.getWalletByIdentifier(jwtClaimsSet.getIssuer()); + + holdersCredentials.forEach(c -> verifiableCredentials.add(c.getData())); + + return buildVP(false, jwtClaimsSet.getAudience().get(0), callerWallet.getBpn(), + callerWallet, verifiableCredentials); + } + + private void checkReadPermission(String permission) { + if (!"read".equals(permission)) { + throw new PermissionViolationException("Scopes must have only READ permission"); + } + } + + private void checkMissingVcs(List missingVCTypes) { + if (!missingVCTypes.isEmpty()) { + throw new MissingVcTypesException(String.format("MissingVCs of types: %s", missingVCTypes)); + } + } } diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java new file mode 100644 index 000000000..33c0067d1 --- /dev/null +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java @@ -0,0 +1,81 @@ +/* + * ******************************************************************************* + * 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 com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import lombok.experimental.UtilityClass; +import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException; + +import java.text.ParseException; +import java.util.Optional; + +@UtilityClass +public class TokenParsingUtils { + + public static final String ACCESS_TOKEN = "access_token"; + public static final String SCOPE = "scope"; + public static final String BEARER_ACCESS_SCOPE = "bearer_access_scope"; + + public static JWTClaimsSet getClaimsSet(SignedJWT tokenParsed) { + try { + return tokenParsed.getJWTClaimsSet(); + } catch (ParseException e) { + throw new BadDataException("Could not parse jwt token", e); + } + } + + public static SignedJWT parseToken(String token) { + try { + return SignedJWT.parse(token); + } catch (ParseException e) { + throw new BadDataException("Could not parse jwt token", e); + } + } + + public static Optional getAccessToken(JWTClaimsSet claims) { + try { + String accessTokenValue = claims.getStringClaim(ACCESS_TOKEN); + return accessTokenValue == null ? Optional.empty() : Optional.of(accessTokenValue); + } catch (ParseException e) { + throw new BadDataException("Could not parse jwt token", e); + } + } + public static SignedJWT getAccessToken(String outerToken) { + SignedJWT jwtOuter = parseToken(outerToken); + JWTClaimsSet claimsSet = getClaimsSet(jwtOuter); + Optional accessToken = getAccessToken(claimsSet); + return accessToken.map(TokenParsingUtils::parseToken).orElse(null); + } + + public static String getScope(JWTClaimsSet jwtClaimsSet) { + try { + String scopes = jwtClaimsSet.getStringClaim(SCOPE); + if (scopes == null) { + scopes = jwtClaimsSet.getStringClaim(BEARER_ACCESS_SCOPE); + } + return scopes; + } catch (ParseException e) { + throw new BadDataException("Token does not contain scope claim"); + } + } +} From 579a5ec547d486f04fca4336545cd9f5bb0dc216 Mon Sep 17 00:00:00 2001 From: aleksandra-bel Date: Thu, 22 Feb 2024 17:21:00 +0100 Subject: [PATCH 02/13] feat: improve filter, add exception handling --- .../config/ExceptionHandling.java | 15 +++++++++++++++ .../config/security/PresentationIatpFilter.java | 14 +++++++++----- .../constant/StringPool.java | 4 ++++ .../service/PresentationService.java | 12 ++++++++---- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/ExceptionHandling.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/ExceptionHandling.java index 408633976..f272c0f25 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/ExceptionHandling.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/ExceptionHandling.java @@ -244,6 +244,21 @@ ProblemDetail handleJsonLdError(JsonLdError exception) { return problemDetail; } + @ExceptionHandler(MissingVcTypesException.class) + ProblemDetail handleMissingVcTypesException(MissingVcTypesException exception) { + ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ExceptionUtils.getMessage(exception)); + problemDetail.setTitle(ExceptionUtils.getMessage(exception)); + problemDetail.setProperty(TIMESTAMP, System.currentTimeMillis()); + return problemDetail; + } + + @ExceptionHandler(PermissionViolationException.class) + ProblemDetail handlePermissionViolationException(PermissionViolationException exception) { + ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.FORBIDDEN, ExceptionUtils.getMessage(exception)); + problemDetail.setTitle(ExceptionUtils.getMessage(exception)); + problemDetail.setProperty(TIMESTAMP, System.currentTimeMillis()); + return problemDetail; + } /** * Handle exception problem detail. diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java index dac29fcb3..61ca5acd0 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/PresentationIatpFilter.java @@ -29,7 +29,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; -import org.eclipse.tractusx.managedidentitywallets.constant.TokenValidationErrors; import org.eclipse.tractusx.managedidentitywallets.dto.ValidationResult; import org.eclipse.tractusx.managedidentitywallets.service.STSTokenValidationService; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -37,9 +36,11 @@ import org.springframework.web.filter.GenericFilterBean; import java.io.IOException; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; +import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COMA_SEPARATOR; + public class PresentationIatpFilter extends GenericFilterBean { RequestMatcher customFilterUrl = new AntPathRequestMatcher(RestURI.API_PRESENTATIONS_IATP); @@ -53,7 +54,6 @@ public PresentationIatpFilter(STSTokenValidationService validationService) { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; @@ -64,8 +64,10 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } else { ValidationResult result = validationService.validateToken(authHeader); if (!result.isValid()) { - List errors = result.getErrors(); - String content = Arrays.toString(errors.toArray()); + List errorValues = new ArrayList<>(); + result.getErrors().forEach(c -> errorValues.add(c.name())); + String content = String.join(COMA_SEPARATOR, errorValues); + httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); httpServletResponse.setContentLength(content.length()); httpServletResponse.getWriter().write(content); @@ -73,6 +75,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha chain.doFilter(request, response); } } + } else { + chain.doFilter(request, response); } } } diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java index 1d05be7d2..27a10a52e 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java @@ -91,4 +91,8 @@ private StringPool() { public static final String BEARER_SPACE = "Bearer "; public static final String BPN_NUMBER_REGEX = "^(BPN)(L|S|A)[0-9A-Z]{12}"; + + public static final String COMA_SEPARATOR = ", "; + public static final String BLANK_SEPARATOR = " "; + public static final String COLON_SEPARATOR = ":"; } diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java index fa99929ae..7ca17b965 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java @@ -69,6 +69,9 @@ import java.util.Map; import java.util.UUID; +import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.BLANK_SEPARATOR; +import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; +import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COMA_SEPARATOR; import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getClaimsSet; import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getScope; @@ -292,17 +295,17 @@ public Map createVpWithRequiredScopes(SignedJWT innerJWT) { JWTClaimsSet jwtClaimsSet = getClaimsSet(innerJWT); String scopeValue = getScope(jwtClaimsSet); - String[] scopes = scopeValue.split(" "); + String[] scopes = scopeValue.split(BLANK_SEPARATOR); for (String scope : scopes) { - String[] scopeParts = scope.split(":"); + String[] scopeParts = scope.split(COLON_SEPARATOR); String vcType = scopeParts[1]; checkReadPermission(scopeParts[2]); List credentials = holdersCredentialRepository.getByHolderDidAndType(jwtClaimsSet.getIssuer(), vcType); if ((null == credentials) || credentials.isEmpty()) { - missingVCTypes.add(String.format("%s MISSING", vcType)); + missingVCTypes.add(vcType); } else { holdersCredentials.addAll(credentials); } @@ -326,7 +329,8 @@ private void checkReadPermission(String permission) { private void checkMissingVcs(List missingVCTypes) { if (!missingVCTypes.isEmpty()) { - throw new MissingVcTypesException(String.format("MissingVCs of types: %s", missingVCTypes)); + throw new MissingVcTypesException(String.format("Missing VC types: %s", + String.join(COMA_SEPARATOR, missingVCTypes))); } } } From 9dc628b3ff4812759e9762f984f0406073191e40 Mon Sep 17 00:00:00 2001 From: aleksandra-bel Date: Mon, 26 Feb 2024 12:06:31 +0100 Subject: [PATCH 03/13] feat: add api docs, option asJwt --- .../PresentationControllerApiDocs.java | 108 +++++++++++++++++- .../controller/PresentationController.java | 11 +- .../service/PresentationService.java | 4 +- 3 files changed, 111 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/PresentationControllerApiDocs.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/PresentationControllerApiDocs.java index 06a072e3a..b2b0c964c 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/PresentationControllerApiDocs.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/PresentationControllerApiDocs.java @@ -1,10 +1,5 @@ package org.eclipse.tractusx.managedidentitywallets.apidocs; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; @@ -13,6 +8,11 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + public class PresentationControllerApiDocs { public static final String API_TAG_VERIFIABLE_PRESENTATIONS_GENERATION = "Verifiable Presentations - Generation"; public static final String API_TAG_VERIFIABLE_PRESENTATIONS_VALIDATION = "Verifiable Presentations - Validation"; @@ -286,4 +286,102 @@ public class PresentationControllerApiDocs { public @interface PostVerifiablePresentationValidationApiDocs { } + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @Tag(name = API_TAG_VERIFIABLE_PRESENTATIONS_GENERATION) + @Operation(summary = "Create Verifiable Presentation", description = "Create a verifiable presentation for the verifiable credential types listed in STS token") + @ApiResponses(value = { + @ApiResponse(responseCode = "401", description = "The request could not be completed due to a failed authorization.", content = { + @Content(examples = { + @ExampleObject(name = "The following errors were found on token validation", value = "TOKEN_ALREADY_EXPIRED, NONCE_MISSING") + }) }), + @ApiResponse(responseCode = "403", description = "The request could not be completed due to a forbidden scope value", content = { + @Content(examples = {}) }), + @ApiResponse(responseCode = "500", description = "Any other internal server error", content = { + @Content(examples = { + @ExampleObject(name = "Internal server error", value = """ + { + "type": "about:blank", + "title": "Error Title", + "status": 500, + "detail": "Error Details", + "instance": "API endpoint", + "properties": { + "timestamp": 1689762476720 + } + } + """) + }) }), + @ApiResponse(responseCode = "404", description = "One or more of the requested verifiable credential types were not found", content = { + @Content(examples = { + @ExampleObject(name = "One or more of the requested verifiable credential types were not found", value = """ + { + "type": "about:blank", + "title": "Error Title", + "status": 404, + "detail": "Verifiable credential types that were not found", + "instance": "API endpoint", + "properties": { + "timestamp": 1689762476720 + } + } + """) + }) }), + @ApiResponse(responseCode = "200", description = "Verifiable Presentation", content = { + @Content(examples = { + @ExampleObject(name = "VP as Json-LD", value = """ + { + "vp": { + "@context": [ + "https://www.w3.org/2018/credentials/v1" + ], + "id": "did:web:localhost:BPNL000000000000#b2e69e47-95f3-48ff-af30-eaaab36431d5", + "type": [ + "VerifiablePresentation" + ], + "verifiableCredential": [ + { + "id": "did:web:localhost:BPNL000000000000#f73e3631-ba87-4a03-bea3-b28700056879", + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://catenax-ng.github.io/product-core-schemas/businessPartnerData.json", + "https://w3id.org/security/suites/jws-2020/v1" + ], + "type": [ + "VerifiableCredential", + "BpnCredential" + ], + "issuer": "did:web:localhost:BPNL000000000000", + "expirationDate": "2024-12-31T18:30:00Z", + "issuanceDate": "2023-07-19T09:11:34Z", + "credentialSubject": [ + { + "bpn": "BPNL000000000000", + "id": "did:web:localhost:BPNL000000000000", + "type": "BpnCredential" + } + ], + "proof": { + "created": "2023-07-19T09:11:39Z", + "jws": "eyJhbGciOiJFZERTQSJ9..fdn2qU85auOltdHDLdHI7sJVV1ZPdftpiXd_ndXN0dFgSDWiIrScdD03wtvKLq_H-shQWfh2RYeMmrlEzAhfDw", + "proofPurpose": "proofPurpose", + "type": "JsonWebSignature2020", + "verificationMethod": "did:web:localhost:BPNL000000000000#" + } + } + ] + } + } + """), + @ExampleObject(name = "VP as JWT", value = """ + { + "vp": "eyJraWQiOiJkaWQ6d2ViOmxvY2FsaG9zdDpCUE5MMDAwMDAwMDAwMDAwIiwidHlwIjoiSldUIiwiYWxnIjoiRWREU0EifQ.eyJzdWIiOiJkaWQ6d2ViOmxvY2FsaG9zdDpCUE5MMDAwMDAwMDAwMDAwIiwiYXVkIjoic21hcnQiLCJpc3MiOiJkaWQ6d2ViOmxvY2FsaG9zdDpCUE5MMDAwMDAwMDAwMDAwIiwidnAiOnsiaWQiOiJkaWQ6d2ViOmxvY2FsaG9zdDpCUE5MMDAwMDAwMDAwMDAwIzM4ZTU2ZTg1LTNkODQtNGEyNS1iZjg1LWFiMjRlYzY4MmMwOSIsInR5cGUiOlsiVmVyaWZpYWJsZVByZXNlbnRhdGlvbiJdLCJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vY2F0ZW5heC1uZy5naXRodWIuaW8vcHJvZHVjdC1jb3JlLXNjaGVtYXMvYnVzaW5lc3NQYXJ0bmVyRGF0YS5qc29uIiwiaHR0cHM6Ly93M2lkLm9yZy9zZWN1cml0eS9zdWl0ZXMvandzLTIwMjAvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIkJwbkNyZWRlbnRpYWwiXSwiaWQiOiJkaWQ6d2ViOmxvY2FsaG9zdDpCUE5MMDAwMDAwMDAwMDAwI2Y3M2UzNjMxLWJhODctNGEwMy1iZWEzLWIyODcwMDA1Njg3OSIsImlzc3VlciI6ImRpZDp3ZWI6bG9jYWxob3N0OkJQTkwwMDAwMDAwMDAwMDAiLCJpc3N1YW5jZURhdGUiOiIyMDIzLTA3LTE5VDA5OjExOjM0WiIsImV4cGlyYXRpb25EYXRlIjoiMjAyNC0xMi0zMVQxODozMDowMFoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDp3ZWI6bG9jYWxob3N0OkJQTkwwMDAwMDAwMDAwMDAiLCJicG4iOiJCUE5MMDAwMDAwMDAwMDAwIiwidHlwZSI6IkJwbkNyZWRlbnRpYWwifSwicHJvb2YiOnsicHJvb2ZQdXJwb3NlIjoicHJvb2ZQdXJwb3NlIiwidHlwZSI6Ikpzb25XZWJTaWduYXR1cmUyMDIwIiwidmVyaWZpY2F0aW9uTWV0aG9kIjoiZGlkOndlYjpsb2NhbGhvc3Q6QlBOTDAwMDAwMDAwMDAwMCMiLCJjcmVhdGVkIjoiMjAyMy0wNy0xOVQwOToxMTozOVoiLCJqd3MiOiJleUpoYkdjaU9pSkZaRVJUUVNKOS4uZmRuMnFVODVhdU9sdGRIRExkSEk3c0pWVjFaUGRmdHBpWGRfbmRYTjBkRmdTRFdpSXJTY2REMDN3dHZLTHFfSC1zaFFXZmgyUlllTW1ybEV6QWhmRHcifX19LCJleHAiOjE2ODk4MzQ4MDUsImp0aSI6ImIwODYzOWZiLWQ5MWEtNGUwZS1iNmY4LTYzYjdhMzQ1ZTRhZiJ9.80x0AB-OauefdeZfx1cwhitdVKRvCRFeFzYwU73DL7y4w34vu6BdfHWLBGjkwELxkQEoFfiTPOqtuyqhtsyDBg" + } + """) + }) + }) + }) + public @interface GetVerifiablePresentationIATPApiDocs { + } + } diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java index d41eae0fd..15b61e277 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java @@ -24,8 +24,8 @@ 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; @@ -102,12 +102,13 @@ public ResponseEntity> validatePresentation(@RequestBody Map * @param stsToken the STS token with required scopes * @return the VP response entity */ - @SneakyThrows + @GetMapping(path = RestURI.API_PRESENTATIONS_IATP, produces = { MediaType.APPLICATION_JSON_VALUE }) - // @SecureTokenControllerApiDoc.PostSecureTokenDoc TODO create API docs - public ResponseEntity> createPresentation(@RequestHeader(name = "Authorization") String stsToken) { + @GetVerifiablePresentationIATPApiDocs + public ResponseEntity> createPresentation(@RequestHeader(name = "Authorization") String stsToken, + @RequestParam(name = "asJwt", required = false, defaultValue = "false") boolean asJwt) { SignedJWT accessToken = getAccessToken(stsToken); - Map vp = presentationService.createVpWithRequiredScopes(accessToken); + Map vp = presentationService.createVpWithRequiredScopes(accessToken, asJwt); return ResponseEntity.ok(vp); } } diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java index 7ca17b965..0db76aec3 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java @@ -288,7 +288,7 @@ private boolean validateCredential(VerifiableCredential credential) { return isValid; } - public Map createVpWithRequiredScopes(SignedJWT innerJWT) { + public Map createVpWithRequiredScopes(SignedJWT innerJWT, boolean asJwt) { List holdersCredentials = new ArrayList<>(); List missingVCTypes = new ArrayList<>(); List verifiableCredentials = new ArrayList<>(); @@ -317,7 +317,7 @@ public Map createVpWithRequiredScopes(SignedJWT innerJWT) { holdersCredentials.forEach(c -> verifiableCredentials.add(c.getData())); - return buildVP(false, jwtClaimsSet.getAudience().get(0), callerWallet.getBpn(), + return buildVP(asJwt, jwtClaimsSet.getAudience().get(0), callerWallet.getBpn(), callerWallet, verifiableCredentials); } From 478e807248b407d0256eafeee870a6dbfd6a7b9f Mon Sep 17 00:00:00 2001 From: aleksandra-bel Date: Mon, 26 Feb 2024 14:32:53 +0100 Subject: [PATCH 04/13] chore: improve api docs --- .../HoldersCredentialControllerApiDocs.java | 5 +++-- .../IssuersCredentialControllerApiDocs.java | 13 +++++++------ .../PresentationControllerApiDocs.java | 11 +++++------ .../apidocs/SecureTokenControllerApiDoc.java | 2 ++ .../apidocs/WalletControllerApiDocs.java | 9 +++++---- .../config/openapi/OpenApiConfig.java | 19 +++++++++++++++++-- .../controller/PresentationController.java | 5 +++-- 7 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/HoldersCredentialControllerApiDocs.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/HoldersCredentialControllerApiDocs.java index ef5c2ead5..2d63ecb57 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/HoldersCredentialControllerApiDocs.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/HoldersCredentialControllerApiDocs.java @@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; public class HoldersCredentialControllerApiDocs { @@ -148,7 +149,7 @@ public class HoldersCredentialControllerApiDocs { """) }) }) }) - @Operation(description = "Permission: **view_wallets** OR **view_wallet** (The BPN of holderIdentifier must equal BPN of caller)\n\n Search verifiable credentials with filter criteria", summary = "Query Verifiable Credentials") + @Operation(description = "Permission: **view_wallets** OR **view_wallet** (The BPN of holderIdentifier must equal BPN of caller)\n\n Search verifiable credentials with filter criteria", summary = "Query Verifiable Credentials", security = { @SecurityRequirement(name = "Authenticate using access_token") }) public @interface GetCredentialsApiDocs { } @@ -278,7 +279,7 @@ public class HoldersCredentialControllerApiDocs { } """)) }) - @Operation(summary = "Issue Verifiable Credential", description = "Permission: **update_wallets** OR **update_wallet** (The BPN of the issuer of the Verifiable Credential must equal BPN of caller)\nIssue a verifiable credential with a given issuer DID") + @Operation(summary = "Issue Verifiable Credential", description = "Permission: **update_wallets** OR **update_wallet** (The BPN of the issuer of the Verifiable Credential must equal BPN of caller)\nIssue a verifiable credential with a given issuer DID", security = { @SecurityRequirement(name = "Authenticate using access_token") }) public @interface IssueCredentialApiDoc { } diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/IssuersCredentialControllerApiDocs.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/IssuersCredentialControllerApiDocs.java index a137f6d85..200f78e8f 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/IssuersCredentialControllerApiDocs.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/IssuersCredentialControllerApiDocs.java @@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; public class IssuersCredentialControllerApiDocs { @@ -192,7 +193,7 @@ public class IssuersCredentialControllerApiDocs { }) }), }) - @Operation(description = "Permission: **view_wallets** (The BPN of holderIdentifier must equal BPN of caller)\n\n Search verifiable credentials with filter criteria", summary = "Query Verifiable Credentials") + @Operation(description = "Permission: **view_wallets** (The BPN of holderIdentifier must equal BPN of caller)\n\n Search verifiable credentials with filter criteria", summary = "Query Verifiable Credentials", security = { @SecurityRequirement(name = "Authenticate using access_token") }) public @interface GetCredentialsApiDocs { } @@ -315,7 +316,7 @@ public class IssuersCredentialControllerApiDocs { """) }) }) }) - @Operation(summary = "Issue a Membership Verifiable Credential with base wallet issuer", description = "Permission: **update_wallets** (The BPN of base wallet must equal BPN of caller)\n\n Issue a verifiable credential by base wallet") + @Operation(summary = "Issue a Membership Verifiable Credential with base wallet issuer", description = "Permission: **update_wallets** (The BPN of base wallet must equal BPN of caller)\n\n Issue a verifiable credential by base wallet", security = { @SecurityRequirement(name = "Authenticate using access_token") }) public @interface IssueMembershipCredentialApiDoc { } @@ -447,7 +448,7 @@ public class IssuersCredentialControllerApiDocs { }) }) }) - @Operation(summary = "Issue a Dismantler Verifiable Credential with base wallet issuer", description = "Permission: **update_wallets** (The BPN of base wallet must equal BPN of caller)\n\n Issue a verifiable credential by base wallet") + @Operation(summary = "Issue a Dismantler Verifiable Credential with base wallet issuer", description = "Permission: **update_wallets** (The BPN of base wallet must equal BPN of caller)\n\n Issue a verifiable credential by base wallet", security = { @SecurityRequirement(name = "Authenticate using access_token") }) public @interface IssueDismantlerCredentialApiDoc { } @@ -515,7 +516,7 @@ public class IssuersCredentialControllerApiDocs { }) }) @Tag(name = API_TAG_VERIFIABLE_CREDENTIAL_ISSUER) - @Operation(summary = "Issue a Use Case Verifiable Credential with base wallet issuer", description = "Permission: **update_wallets** (The BPN of base wallet must equal BPN of caller)\n\n Issue a verifiable credential by base wallet") + @Operation(summary = "Issue a Use Case Verifiable Credential with base wallet issuer", description = "Permission: **update_wallets** (The BPN of base wallet must equal BPN of caller)\n\n Issue a verifiable credential by base wallet", security = { @SecurityRequirement(name = "Authenticate using access_token") }) @ApiResponses(value = { @ApiResponse(responseCode = "401", description = "The request could not be completed due to a failed authorization.", content = { @Content(examples = {}) }), @@ -943,7 +944,7 @@ public class IssuersCredentialControllerApiDocs { """) }) }) }) - @Operation(summary = "Validate Verifiable Credentials", description = "Permission: **view_wallets** OR **view_wallet** \n\n Validate Verifiable Credentials") + @Operation(summary = "Validate Verifiable Credentials", description = "Permission: **view_wallets** OR **view_wallet** \n\n Validate Verifiable Credentials", security = { @SecurityRequirement(name = "Authenticate using access_token") }) @RequestBody(content = { @Content(examples = @ExampleObject(""" { @@ -1074,7 +1075,7 @@ public class IssuersCredentialControllerApiDocs { """) }) }) }) - @Operation(summary = "Issue Verifiable Credential", description = "Permission: **update_wallets** (The BPN of the base wallet must equal BPN of caller)\nIssue a verifiable credential with a given issuer DID") + @Operation(summary = "Issue Verifiable Credential", description = "Permission: **update_wallets** (The BPN of the base wallet must equal BPN of caller)\nIssue a verifiable credential with a given issuer DID", security = { @SecurityRequirement(name = "Authenticate using access_token") }) @RequestBody(content = { @Content(examples = @ExampleObject(""" { diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/PresentationControllerApiDocs.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/PresentationControllerApiDocs.java index b2b0c964c..1d8b3fa39 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/PresentationControllerApiDocs.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/PresentationControllerApiDocs.java @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import java.lang.annotation.ElementType; @@ -20,7 +21,7 @@ public class PresentationControllerApiDocs { @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Tag(name = API_TAG_VERIFIABLE_PRESENTATIONS_GENERATION) - @Operation(summary = "Create Verifiable Presentation", description = "Permission: **update_wallets** OR **update_wallet** (The BPN of the issuer of the Verifiable Presentation must equal to BPN of caller) \n\n Create a verifiable presentation from a list of verifiable credentials, signed by the holder") + @Operation(summary = "Create Verifiable Presentation", description = "Permission: **update_wallets** OR **update_wallet** (The BPN of the issuer of the Verifiable Presentation must equal to BPN of caller) \n\n Create a verifiable presentation from a list of verifiable credentials, signed by the holder", security = { @SecurityRequirement(name = "Authenticate using access_token") }) @ApiResponses(value = { @ApiResponse(responseCode = "401", description = "The request could not be completed due to a failed authorization.", content = { @@ -155,7 +156,7 @@ public class PresentationControllerApiDocs { @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Tag(name = API_TAG_VERIFIABLE_PRESENTATIONS_VALIDATION) - @Operation(summary = "Validate Verifiable Presentation", description = "Permission: **view_wallets** OR **view_wallet** \n\n Validate Verifiable Presentation with all included credentials") + @Operation(summary = "Validate Verifiable Presentation", description = "Permission: **view_wallets** OR **view_wallet** \n\n Validate Verifiable Presentation with all included credentials", security = { @SecurityRequirement(name = "Authenticate using access_token") }) @ApiResponses(value = { @ApiResponse(responseCode = "401", description = "The request could not be completed due to a failed authorization.", content = { @Content(examples = {}) }), @@ -289,12 +290,10 @@ public class PresentationControllerApiDocs { @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Tag(name = API_TAG_VERIFIABLE_PRESENTATIONS_GENERATION) - @Operation(summary = "Create Verifiable Presentation", description = "Create a verifiable presentation for the verifiable credential types listed in STS token") + @Operation(summary = "Create Verifiable Presentation", description = "Create a verifiable presentation for the verifiable credential types listed in STS token", security = { @SecurityRequirement(name = "sts_token") }) @ApiResponses(value = { @ApiResponse(responseCode = "401", description = "The request could not be completed due to a failed authorization.", content = { - @Content(examples = { - @ExampleObject(name = "The following errors were found on token validation", value = "TOKEN_ALREADY_EXPIRED, NONCE_MISSING") - }) }), + @Content(examples = {}) }), @ApiResponse(responseCode = "403", description = "The request could not be completed due to a forbidden scope value", content = { @Content(examples = {}) }), @ApiResponse(responseCode = "500", description = "Any other internal server error", content = { diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/SecureTokenControllerApiDoc.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/SecureTokenControllerApiDoc.java index 7496840b5..587a8352c 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/SecureTokenControllerApiDoc.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/SecureTokenControllerApiDoc.java @@ -27,6 +27,7 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirements; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -37,6 +38,7 @@ public class SecureTokenControllerApiDoc { @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) + @SecurityRequirements @RequestBody(content = { @Content(examples = { @ExampleObject(name = "Request Secure Token using Scopes", value = """ diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/WalletControllerApiDocs.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/WalletControllerApiDocs.java index e681a14bb..09408965f 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/WalletControllerApiDocs.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/WalletControllerApiDocs.java @@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; public class WalletControllerApiDocs { @@ -120,13 +121,13 @@ public class WalletControllerApiDocs { }) }) }) - @Operation(summary = "Create Wallet", description = "Permission: **add_wallets** (The BPN of the base wallet must equal BPN of caller)\n\n Create a wallet and store it") + @Operation(summary = "Create Wallet", description = "Permission: **add_wallets** (The BPN of the base wallet must equal BPN of caller)\n\n Create a wallet and store it", security = { @SecurityRequirement(name = "Authenticate using access_token") }) public @interface CreateWalletApiDoc { } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) - @Operation(summary = "Store Verifiable Credential", description = "Permission: **update_wallets** OR **update_wallet** (The BPN of wallet to extract credentials from must equal BPN of caller) \n\n Store a verifiable credential in the wallet of the given identifier") + @Operation(summary = "Store Verifiable Credential", description = "Permission: **update_wallets** OR **update_wallet** (The BPN of wallet to extract credentials from must equal BPN of caller) \n\n Store a verifiable credential in the wallet of the given identifier", security = { @SecurityRequirement(name = "Authenticate using access_token") }) @RequestBody(content = { @Content(examples = @ExampleObject(""" { @@ -396,7 +397,7 @@ public class WalletControllerApiDocs { } """) }) }) }) - @Operation(summary = "Retrieve wallet by BPN", description = "Permission: **view_wallets** OR **view_wallet** (The BPN of Wallet to retrieve must equal the BPN of caller or Base wallet, authority wallet can see all wallets) \n\n Retrieve single wallet by identifier, with or without its credentials") + @Operation(summary = "Retrieve wallet by BPN", description = "Permission: **view_wallets** OR **view_wallet** (The BPN of Wallet to retrieve must equal the BPN of caller or Base wallet, authority wallet can see all wallets) \n\n Retrieve single wallet by identifier, with or without its credentials", security = { @SecurityRequirement(name = "Authenticate using access_token") }) public @interface RetrieveWalletApiDoc { } @@ -503,7 +504,7 @@ public class WalletControllerApiDocs { }) }) }) - @Operation(summary = "List of wallets", description = "Permission: **view_wallets** \n\n Retrieve list of registered wallets") + @Operation(summary = "List of wallets", description = "Permission: **view_wallets** \n\n Retrieve list of registered wallets", security = { @SecurityRequirement(name = "Authenticate using access_token") }) public @interface RetrieveWalletsApiDoc { } diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/openapi/OpenApiConfig.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/openapi/OpenApiConfig.java index 76dd3f645..0345ae091 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/openapi/OpenApiConfig.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/openapi/OpenApiConfig.java @@ -106,8 +106,23 @@ private OpenAPI enableSecurity(OpenAPI openAPI) { "\n" + "Example: Bearer 12345abcdef") .type(SecurityScheme.Type.APIKEY).in(SecurityScheme.In.HEADER).name(HttpHeaders.AUTHORIZATION)); + + //Auth using sts_token + String stsTokenAuth = "sts_token"; + components.addSecuritySchemes(stsTokenAuth, + new SecurityScheme().name(stsTokenAuth) + .description("**STS token** \n" + + "JWT Authorization header\n" + + "\n" + + "Enter your token in the text input below.\n" + + "\n" + + "Example: 12345abcdef") + .type(SecurityScheme.Type.APIKEY).in(SecurityScheme.In.HEADER).name(HttpHeaders.AUTHORIZATION)); + return openAPI.components(components) .addSecurityItem(new SecurityRequirement() - .addList(accessTokenAuth, Collections.emptyList())); + .addList(accessTokenAuth, Collections.emptyList())) + .addSecurityItem(new SecurityRequirement() + .addList(stsTokenAuth, Collections.emptyList())); } -} \ No newline at end of file +} diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java index 15b61e277..a0e267c79 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationController.java @@ -99,13 +99,14 @@ 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 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 }) @GetVerifiablePresentationIATPApiDocs - public ResponseEntity> createPresentation(@RequestHeader(name = "Authorization") String stsToken, + 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); From a6c2154304b512b9f6c6c26d3a4aaa70c439c820 Mon Sep 17 00:00:00 2001 From: aleksandra-bel Date: Mon, 26 Feb 2024 14:57:20 +0100 Subject: [PATCH 05/13] chore: improve api docs --- .../apidocs/DidDocumentControllerApiDocs.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/DidDocumentControllerApiDocs.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/DidDocumentControllerApiDocs.java index 1a53d27de..385f61541 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/DidDocumentControllerApiDocs.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/apidocs/DidDocumentControllerApiDocs.java @@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; public class DidDocumentControllerApiDocs { @@ -72,7 +73,7 @@ public class DidDocumentControllerApiDocs { """) }) }) }) - @Operation(description = "Resolve the DID document for a given DID or BPN", summary = "Resolve DID Document") + @Operation(description = "Resolve the DID document for a given DID or BPN", summary = "Resolve DID Document", security = { @SecurityRequirement(name = "Authenticate using access_token") }) public @interface GetDidDocumentApiDocs { } @@ -135,7 +136,7 @@ public class DidDocumentControllerApiDocs { }) }) }) - @Operation(description = "Resolve the DID document for a given BPN", summary = "Resolve DID Document") + @Operation(description = "Resolve the DID document for a given BPN", summary = "Resolve DID Document", security = { @SecurityRequirement(name = "Authenticate using access_token") }) public @interface GetDidResolveApiDocs { } From 1785080a7f3585ecf6fcb89cd94d91701c4906b9 Mon Sep 17 00:00:00 2001 From: aleksandra-bel Date: Tue, 27 Feb 2024 10:29:51 +0100 Subject: [PATCH 06/13] feat: add ignoring version --- .../managedidentitywallets/constant/StringPool.java | 1 + .../service/PresentationService.java | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java index 27a10a52e..cb8c07e3c 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/constant/StringPool.java @@ -95,4 +95,5 @@ private StringPool() { public static final String COMA_SEPARATOR = ", "; public static final String BLANK_SEPARATOR = " "; public static final String COLON_SEPARATOR = ":"; + public static final String UNDERSCORE = "_"; } diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java index 0db76aec3..d3b09c055 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/PresentationService.java @@ -72,6 +72,7 @@ import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.BLANK_SEPARATOR; import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COLON_SEPARATOR; import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.COMA_SEPARATOR; +import static org.eclipse.tractusx.managedidentitywallets.constant.StringPool.UNDERSCORE; import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getClaimsSet; import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getScope; @@ -301,11 +302,12 @@ public Map createVpWithRequiredScopes(SignedJWT innerJWT, boolea String[] scopeParts = scope.split(COLON_SEPARATOR); String vcType = scopeParts[1]; checkReadPermission(scopeParts[2]); + String vcTypeNoVersion = removeVersion(vcType); List credentials = - holdersCredentialRepository.getByHolderDidAndType(jwtClaimsSet.getIssuer(), vcType); + holdersCredentialRepository.getByHolderDidAndType(jwtClaimsSet.getIssuer(), vcTypeNoVersion); if ((null == credentials) || credentials.isEmpty()) { - missingVCTypes.add(vcType); + missingVCTypes.add(vcTypeNoVersion); } else { holdersCredentials.addAll(credentials); } @@ -333,4 +335,9 @@ private void checkMissingVcs(List missingVCTypes) { String.join(COMA_SEPARATOR, missingVCTypes))); } } + + private String removeVersion(String vcType) { + String[] parts = vcType.split(UNDERSCORE); + return (parts.length > 1) ? parts[0] : vcType; + } } From ec293b0f0f0dc01a5d2d1ade2adf7caed34b4148 Mon Sep 17 00:00:00 2001 From: aleksandra-bel Date: Tue, 27 Feb 2024 13:45:37 +0100 Subject: [PATCH 07/13] chore: move logic to token validation utils --- .../service/STSTokenValidationService.java | 30 ++----------------- .../utils/TokenParsingUtils.java | 8 +++-- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationService.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationService.java index 74b7659ef..93df35ae8 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationService.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationService.java @@ -38,6 +38,9 @@ import java.util.List; import java.util.Optional; +import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getAccessToken; +import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getClaimsSet; +import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.parseToken; import static org.eclipse.tractusx.managedidentitywallets.utils.TokenValidationUtils.NONCE; @Service @@ -48,8 +51,6 @@ public class STSTokenValidationService { private final DidDocumentResolverService didDocumentResolverService; private final CustomSignedJWTVerifier customSignedJWTverifier; private final TokenValidationUtils tokenValidationUtils; - private static final String ACCESS_TOKEN = "access_token"; - private static final String PARSING_TOKEN_ERROR = "Could not parse jwt token"; /** * Validates SI token and Access token. @@ -91,31 +92,6 @@ public ValidationResult validateToken(String token) { return combineValidationResults(validationResults); } - private JWTClaimsSet getClaimsSet(SignedJWT tokenParsed) { - try { - return tokenParsed.getJWTClaimsSet(); - } catch (ParseException e) { - throw new BadDataException(PARSING_TOKEN_ERROR, e); - } - } - - private SignedJWT parseToken(String token) { - try { - return SignedJWT.parse(token); - } catch (ParseException e) { - throw new BadDataException(PARSING_TOKEN_ERROR, e); - } - } - - private Optional getAccessToken(JWTClaimsSet claims) { - try { - String accessTokenValue = claims.getStringClaim(ACCESS_TOKEN); - return accessTokenValue == null ? Optional.empty() : Optional.of(accessTokenValue); - } catch (ParseException e) { - throw new BadDataException(PARSING_TOKEN_ERROR, e); - } - } - private ValidationResult verifySignature(String did, SignedJWT signedJWT) { try { customSignedJWTverifier.setDidResolver(didDocumentResolverService.getCompositeDidResolver()); diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java index 33c0067d1..35a3dcca0 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/utils/TokenParsingUtils.java @@ -33,6 +33,7 @@ public class TokenParsingUtils { public static final String ACCESS_TOKEN = "access_token"; + public static final String PARSING_TOKEN_ERROR = "Could not parse jwt token"; public static final String SCOPE = "scope"; public static final String BEARER_ACCESS_SCOPE = "bearer_access_scope"; @@ -40,7 +41,7 @@ public static JWTClaimsSet getClaimsSet(SignedJWT tokenParsed) { try { return tokenParsed.getJWTClaimsSet(); } catch (ParseException e) { - throw new BadDataException("Could not parse jwt token", e); + throw new BadDataException(PARSING_TOKEN_ERROR, e); } } @@ -48,7 +49,7 @@ public static SignedJWT parseToken(String token) { try { return SignedJWT.parse(token); } catch (ParseException e) { - throw new BadDataException("Could not parse jwt token", e); + throw new BadDataException(PARSING_TOKEN_ERROR, e); } } @@ -57,9 +58,10 @@ public static Optional getAccessToken(JWTClaimsSet claims) { String accessTokenValue = claims.getStringClaim(ACCESS_TOKEN); return accessTokenValue == null ? Optional.empty() : Optional.of(accessTokenValue); } catch (ParseException e) { - throw new BadDataException("Could not parse jwt token", e); + throw new BadDataException(PARSING_TOKEN_ERROR, e); } } + public static SignedJWT getAccessToken(String outerToken) { SignedJWT jwtOuter = parseToken(outerToken); JWTClaimsSet claimsSet = getClaimsSet(jwtOuter); From eac5a994a7ec22ce619332f830fdfeef3e436a0e Mon Sep 17 00:00:00 2001 From: aleksandra-bel Date: Tue, 27 Feb 2024 19:14:11 +0100 Subject: [PATCH 08/13] chore: add test for custom web filter --- .../PresentationIatpFilterTest.java | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/test/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationIatpFilterTest.java diff --git a/src/test/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationIatpFilterTest.java b/src/test/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationIatpFilterTest.java new file mode 100644 index 000000000..ed2555bdd --- /dev/null +++ b/src/test/java/org/eclipse/tractusx/managedidentitywallets/controller/PresentationIatpFilterTest.java @@ -0,0 +1,108 @@ +/* + * ******************************************************************************* + * 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.controller; + +import org.eclipse.tractusx.managedidentitywallets.ManagedIdentityWalletsApplication; +import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer; +import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; +import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.dto.ValidationResult; +import org.eclipse.tractusx.managedidentitywallets.service.STSTokenValidationService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +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.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; + +import java.util.List; +import java.util.Map; + +import static org.eclipse.tractusx.managedidentitywallets.constant.TokenValidationErrors.NONCE_MISSING; +import static org.eclipse.tractusx.managedidentitywallets.constant.TokenValidationErrors.TOKEN_ALREADY_EXPIRED; + +@DirtiesContext +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class }) +@ContextConfiguration(initializers = { TestContextInitializer.class }) +class PresentationIatpFilterTest { + + private static final String TOKEN = "eyJraWQiOiI1OGNiNGIzMi1jMmU0LTQ2ZjAtYTNhZC0zMjg2ZTM0NzY1ZWQiLCJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJhY2Nlc3NfdG9rZW4iOiJleUpyYVdRaU9pSTFPR05pTkdJek1pMWpNbVUwTFRRMlpqQXRZVE5oWkMwek1qZzJaVE0wTnpZMVpXUWlMQ0owZVhBaU9pSktWMVFpTENKaGJHY2lPaUpGWkVSVFFTSjkuZXlKaGRXUWlPaUprYVdRNmQyVmlPbXh2WTJGc2FHOXpkRHBDVUU1TU1EQXdNREF3TURBd01EQXdJaXdpYzNWaUlqb2laR2xrT25kbFlqcHNiMk5oYkdodmMzUTZRbEJPVERBd01EQXdNREF3TURBd01DSXNJbk5qYjNCbElqb2liM0puTG1WamJHbHdjMlV1ZEhKaFkzUjFjM2d1ZG1NdWRIbHdaVHBXWVd4cFpFTnlaV1JsYm5ScFlXeFVlWEJsT25KbFlXUWlMQ0pwYzNNaU9pSmthV1E2ZDJWaU9teHZZMkZzYUc5emREcENVRTVNTURBd01EQXdNREF3TURBd0lpd2laWGh3SWpveE56QTNNak13TkRVNUxDSnBZWFFpT2pFM01EY3lNekF6T1Rrc0ltcDBhU0k2SW1FNU16YzJNakk0TFRreVpUSXROR1pqT0MwNVpUZ3pMVGMxWlRneFpEVm1OR1V3TXlKOS40WHBKVTl0VlQ5QU4zT2JYdHZOX2hGcTNqY2Z0QjMwR2tJOXJHUWhBbFA2MnB5eFNZeDZTRENEVkJTbmpQTUE0MVB3cXIzaC1OVVVtcmFVU2dvUXNBZyIsImF1ZCI6ImRpZDp3ZWI6bG9jYWxob3N0OkJQTkwwMDAwMDAwMDAwMDAiLCJzdWIiOiJkaWQ6d2ViOmxvY2FsaG9zdDpCUE5MMDAwMDAwMDAwMDAwIiwiaXNzIjoiZGlkOndlYjpsb2NhbGhvc3Q6QlBOTDAwMDAwMDAwMDAwMCIsImV4cCI6MTcwNzIzMDQ1OSwiaWF0IjoxNzA3MjMwMzk5LCJqdGkiOiJhYWQ4OTUzMS04YjE4LTQzN2EtOGZmNS1lZDc2OThjMmFlYTAifQ.HXPtWRDh6rIlYdhQq40zLmeLhWgQnj_EwHYZ014AuTJhSgEmTep756nNyTcMXqa-cloNxoKrA323VLcaOAezBQ"; + + @MockBean + private STSTokenValidationService validationService; + + @Autowired + private TestRestTemplate testTemplate; + + @Test + void createPresentationFailure401Test() { + HttpHeaders headers = new HttpHeaders(); + headers.put(HttpHeaders.CONTENT_TYPE, List.of(MediaType.APPLICATION_JSON_VALUE)); + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity> response = testTemplate.exchange( + RestURI.API_PRESENTATIONS_IATP, + HttpMethod.GET, + entity, + new ParameterizedTypeReference<>() { + } + ); + + Assertions.assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + } + + @Test + void createPresentationFailure401WithErrorsTest() { + HttpHeaders headers = new HttpHeaders(); + headers.put(HttpHeaders.CONTENT_TYPE, List.of(MediaType.APPLICATION_JSON_VALUE)); + headers.put(HttpHeaders.AUTHORIZATION, List.of(TOKEN)); + HttpEntity entity = new HttpEntity<>(headers); + ValidationResult validationResult = ValidationResult.builder() + .isValid(false) + .errors(List.of(TOKEN_ALREADY_EXPIRED, NONCE_MISSING)) + .build(); + + Mockito.when(validationService.validateToken(TOKEN)).thenReturn(validationResult); + + ResponseEntity response = testTemplate.exchange( + RestURI.API_PRESENTATIONS_IATP, + HttpMethod.GET, + entity, + new ParameterizedTypeReference<>() { + } + ); + + String expectedBody = TOKEN_ALREADY_EXPIRED.name() + StringPool.COMA_SEPARATOR + NONCE_MISSING.name(); + + Assertions.assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + Assertions.assertEquals(expectedBody, response.getBody()); + } +} From 74495d3f6e0f011065828fc2d3efd2402d1d4e83 Mon Sep 17 00:00:00 2001 From: aleksandra-bel Date: Tue, 27 Feb 2024 20:08:31 +0100 Subject: [PATCH 09/13] chore: clean up configs --- .../config/openapi/OpenApiConfig.java | 24 +++++++++---------- .../config/security/SecurityConfig.java | 7 ++---- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/openapi/OpenApiConfig.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/openapi/OpenApiConfig.java index 0345ae091..957aad231 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/openapi/OpenApiConfig.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/openapi/OpenApiConfig.java @@ -99,24 +99,24 @@ private OpenAPI enableSecurity(OpenAPI openAPI) { String accessTokenAuth = "Authenticate using access_token"; components.addSecuritySchemes(accessTokenAuth, new SecurityScheme().name(accessTokenAuth) - .description("**Bearer (apiKey)** \n" + - "JWT Authorization header using the Bearer scheme.\n" + - "\n" + - "Enter **Bearer** [space] and then your token in the text input below.\n" + - "\n" + - "Example: Bearer 12345abcdef") + .description(""" + **Bearer (apiKey)** + JWT Authorization header using the Bearer scheme. + Enter **Bearer** [space] and then your token in the text input below: + Example: Bearer 12345abcdef + """) .type(SecurityScheme.Type.APIKEY).in(SecurityScheme.In.HEADER).name(HttpHeaders.AUTHORIZATION)); //Auth using sts_token String stsTokenAuth = "sts_token"; components.addSecuritySchemes(stsTokenAuth, new SecurityScheme().name(stsTokenAuth) - .description("**STS token** \n" + - "JWT Authorization header\n" + - "\n" + - "Enter your token in the text input below.\n" + - "\n" + - "Example: 12345abcdef") + .description(""" + **STS token** + JWT Authorization header. + Enter your token in the text input below: + Example: 12345abcdef + """) .type(SecurityScheme.Type.APIKEY).in(SecurityScheme.In.HEADER).name(HttpHeaders.AUTHORIZATION)); return openAPI.components(components) diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java index 6d81af27a..17e4ee998 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/config/security/SecurityConfig.java @@ -26,7 +26,6 @@ import org.eclipse.tractusx.managedidentitywallets.constant.ApplicationRole; import org.eclipse.tractusx.managedidentitywallets.constant.RestURI; import org.eclipse.tractusx.managedidentitywallets.service.STSTokenValidationService; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -54,7 +53,6 @@ @AllArgsConstructor public class SecurityConfig { - @Autowired private final STSTokenValidationService validationService; private final SecurityConfigProperties securityConfigProperties; @@ -116,9 +114,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { //error .requestMatchers(new AntPathRequestMatcher("/error")).permitAll() ).oauth2ResourceServer(resourceServer -> resourceServer.jwt(jwt -> - jwt.jwtAuthenticationConverter(new CustomAuthenticationConverter(securityConfigProperties.clientId())))); - - http.addFilterAfter(new PresentationIatpFilter(validationService), BasicAuthenticationFilter.class); + jwt.jwtAuthenticationConverter(new CustomAuthenticationConverter(securityConfigProperties.clientId())))) + .addFilterAfter(new PresentationIatpFilter(validationService), BasicAuthenticationFilter.class); return http.build(); } From 2f2e4988724e5af6dd45a3281ba4e5b5244660a6 Mon Sep 17 00:00:00 2001 From: aleksandra-bel Date: Thu, 29 Feb 2024 09:27:56 +0100 Subject: [PATCH 10/13] chore: fix exception classes --- .../exception/MissingVcTypesException.java | 4 ++++ .../exception/PermissionViolationException.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/MissingVcTypesException.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/MissingVcTypesException.java index c7994ffb2..c6f150d4a 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/MissingVcTypesException.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/MissingVcTypesException.java @@ -21,7 +21,11 @@ package org.eclipse.tractusx.managedidentitywallets.exception; +import java.io.Serial; + public class MissingVcTypesException extends RuntimeException { + @Serial + private static final long serialVersionUID = 1L; public MissingVcTypesException(String message) { super(message); diff --git a/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/PermissionViolationException.java b/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/PermissionViolationException.java index ae868e007..7208d8fb8 100644 --- a/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/PermissionViolationException.java +++ b/src/main/java/org/eclipse/tractusx/managedidentitywallets/exception/PermissionViolationException.java @@ -21,7 +21,11 @@ package org.eclipse.tractusx.managedidentitywallets.exception; +import java.io.Serial; + public class PermissionViolationException extends RuntimeException { + @Serial + private static final long serialVersionUID = 1L; public PermissionViolationException(String message) { super(message); From 7e518773f5932d98780d81aa70fba491e7eb7529 Mon Sep 17 00:00:00 2001 From: andreibogus Date: Thu, 29 Feb 2024 09:48:37 +0100 Subject: [PATCH 11/13] chore: improve test coverage --- .../STSTokenValidationServiceTest.java | 16 +- .../utils/TestConstants.java | 4 + .../utils/TestUtils.java | 3 +- .../vp/PresentationServiceTest.java | 263 ++++++++++++++++++ 4 files changed, 278 insertions(+), 8 deletions(-) create mode 100644 src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java diff --git a/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationServiceTest.java b/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationServiceTest.java index aa2c3d225..9ad6554a7 100644 --- a/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationServiceTest.java +++ b/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationServiceTest.java @@ -52,6 +52,8 @@ import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_BPN_2; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_JSON_STRING_1; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_JSON_STRING_2; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.NONCE; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.READ_SCOPE; import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.addAccessTokenToClaimsSet; import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildClaimsSet; import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildJWTToken; @@ -115,7 +117,7 @@ public void cleanWallets() { @Test void validateTokenFailureAccessTokenMissingTest() throws JOSEException { - JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, "123456", EXP_VALID_DATE, IAT_VALID_DATE); + JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); String siToken = buildJWTToken(JWK_OUTER, outerSet); ValidationResult result = stsTokenValidationService.validateToken(siToken); @@ -131,10 +133,10 @@ void validateTokenFailureWrongSignatureInnerTokenTest() throws JOSEException { .keyID("58cb4b32-c2e4-46f0-a3ad-3286e34765ty") .generate(); - JWTClaimsSet innerSet = buildClaimsSet(DID_BPN_2, DID_BPN_1, DID_BPN_1, "123456", EXP_VALID_DATE, IAT_VALID_DATE); + JWTClaimsSet innerSet = buildClaimsSet(DID_BPN_2, DID_BPN_1, DID_BPN_1, NONCE, READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); String accessToken = buildJWTToken(jwkRandom, innerSet); - JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, "123456", EXP_VALID_DATE, ALREADY_EXP_DATE); + JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, READ_SCOPE, EXP_VALID_DATE, ALREADY_EXP_DATE); JWTClaimsSet outerSetFull = addAccessTokenToClaimsSet(accessToken, outerSet); String siToken = buildJWTToken(JWK_OUTER, outerSetFull); @@ -146,10 +148,10 @@ void validateTokenFailureWrongSignatureInnerTokenTest() throws JOSEException { @Test void validateTokenFailureExpiredTokenIssNotEqualsSubTest() throws JOSEException { - JWTClaimsSet innerSet = buildClaimsSet(DID_BPN_2, DID_BPN_1, DID_BPN_1, "123456", EXP_VALID_DATE, IAT_VALID_DATE); + JWTClaimsSet innerSet = buildClaimsSet(DID_BPN_2, DID_BPN_1, DID_BPN_1, NONCE, READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); String accessToken = buildJWTToken(JWK_INNER, innerSet); - JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_2, DID_BPN_1, "123456", ALREADY_EXP_DATE, IAT_VALID_DATE); + JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_2, DID_BPN_1, NONCE, READ_SCOPE, ALREADY_EXP_DATE, IAT_VALID_DATE); JWTClaimsSet outerSetFull = addAccessTokenToClaimsSet(accessToken, outerSet); String siToken = buildJWTToken(JWK_OUTER, outerSetFull); @@ -162,10 +164,10 @@ void validateTokenFailureExpiredTokenIssNotEqualsSubTest() throws JOSEException @Test void validateTokenSuccessTest() throws JOSEException { - JWTClaimsSet innerSet = buildClaimsSet(DID_BPN_2, DID_BPN_1, DID_BPN_1, "123456", EXP_VALID_DATE, IAT_VALID_DATE); + JWTClaimsSet innerSet = buildClaimsSet(DID_BPN_2, DID_BPN_1, DID_BPN_1, NONCE, READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); String accessToken = buildJWTToken(JWK_INNER, innerSet); - JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, "123456", EXP_VALID_DATE, IAT_VALID_DATE); + JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); JWTClaimsSet outerSetFull = addAccessTokenToClaimsSet(accessToken, outerSet); String siToken = buildJWTToken(JWK_OUTER, outerSetFull); diff --git a/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestConstants.java b/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestConstants.java index c9097a244..36eb0feaa 100644 --- a/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestConstants.java +++ b/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestConstants.java @@ -27,6 +27,10 @@ public class TestConstants { public static final String DID_BPN_2 = "did:web:localhost:BPNL000000000002"; public static final String BPN_1 = "BPNL000000000001"; public static final String BPN_2 = "BPNL000000000002"; + public static final String READ_SCOPE = "org.eclipse.tractusx.vc.type:BpnCredential:read"; + public static final String WRITE_SCOPE = "org.eclipse.tractusx.vc.type:BpnCredential:write"; + public static final String VERIFIABLE_PRESENTATION = "vp"; + public static final String NONCE = "123456"; public static final String DID_JSON_STRING_1 = """ { "@context": [ diff --git a/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestUtils.java b/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestUtils.java index 2f67c1800..8d81763c3 100644 --- a/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestUtils.java +++ b/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestUtils.java @@ -226,7 +226,7 @@ public static String buildJWTToken(OctetKeyPair jwk, JWTClaimsSet claimsSet) thr return signedJWT.serialize(); } - public static JWTClaimsSet buildClaimsSet(String issuer, String subject, String audience, String nonce, Date expiration, Date issuance) { + public static JWTClaimsSet buildClaimsSet(String issuer, String subject, String audience, String nonce, String scope, Date expiration, Date issuance) { return new JWTClaimsSet.Builder() .issuer(issuer) .subject(subject) @@ -234,6 +234,7 @@ public static JWTClaimsSet buildClaimsSet(String issuer, String subject, String .expirationTime(expiration) .issueTime(issuance) .claim("nonce", nonce) + .claim("scope", scope) .build(); } diff --git a/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java b/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java new file mode 100644 index 000000000..598b03bfa --- /dev/null +++ b/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java @@ -0,0 +1,263 @@ +/* + * ******************************************************************************* + * 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.vp; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.jwk.OctetKeyPair; +import com.nimbusds.jose.util.Base64URL; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.JWTParser; +import com.nimbusds.jwt.SignedJWT; +import lombok.SneakyThrows; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemWriter; +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.MIWVerifiableCredentialType; +import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; +import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential; +import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; +import org.eclipse.tractusx.managedidentitywallets.dao.entity.WalletKey; +import org.eclipse.tractusx.managedidentitywallets.dao.repository.HoldersCredentialRepository; +import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletRepository; +import org.eclipse.tractusx.managedidentitywallets.exception.MissingVcTypesException; +import org.eclipse.tractusx.managedidentitywallets.exception.PermissionViolationException; +import org.eclipse.tractusx.managedidentitywallets.service.PresentationService; +import org.eclipse.tractusx.managedidentitywallets.service.WalletKeyService; +import org.eclipse.tractusx.managedidentitywallets.utils.CommonUtils; +import org.eclipse.tractusx.managedidentitywallets.utils.EncryptionUtils; +import org.eclipse.tractusx.ssi.lib.crypt.IKeyGenerator; +import org.eclipse.tractusx.ssi.lib.crypt.KeyPair; +import org.eclipse.tractusx.ssi.lib.crypt.x21559.x21559Generator; +import org.eclipse.tractusx.ssi.lib.exception.KeyGenerationException; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialSubject; +import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialType; +import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentation; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ContextConfiguration; + +import java.io.StringWriter; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static com.nimbusds.jose.jwk.Curve.Ed25519; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.BPN_1; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_BPN_1; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_BPN_2; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_JSON_STRING_1; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.NONCE; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.READ_SCOPE; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.VERIFIABLE_PRESENTATION; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.WRITE_SCOPE; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.addAccessTokenToClaimsSet; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildClaimsSet; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildJWTToken; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildWallet; +import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getAccessToken; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { + ManagedIdentityWalletsApplication.class }) +@ContextConfiguration(initializers = { TestContextInitializer.class }) +public class PresentationServiceTest { + @Autowired + private HoldersCredentialRepository holdersCredentialRepository; + + @Autowired + private WalletRepository walletRepository; + + @Autowired + private WalletKeyService walletKeyService; + + @Autowired + private EncryptionUtils encryptionUtils; + + @Autowired + private MIWSettings miwSettings; + + @Autowired + private PresentationService presentationService; + + private static final OctetKeyPair JWK_OUTER = new OctetKeyPair + .Builder(Ed25519, new Base64URL("4Q5HCXPyutfcj7gLmbAKlYttlJPkykIkRjh7DH2NtZ0")) + .d(new Base64URL("Ktp0sv9dKr_gnzRxpH5V9qpiTgZ1WbkMSv8WtWodewg")) + .keyID("58cb4b32-c2e4-46f0-a3ad-3286e34765ed") + .build(); + + private static final OctetKeyPair JWK_INNER = new OctetKeyPair + .Builder(Ed25519, new Base64URL("Z-8DEkN6pw2E01niDWqrp1kROLF-syIPIpFgmyrVUOU")) + .d(new Base64URL("MLYxSai_oFzuqEfnB2diA3oDuixLg3kQzZKMyW31-2o")) + .keyID("58cb4b32-c2e4-46f0-a3ad-3286e34765ty") + .build(); + + private static final Date EXP_VALID_DATE = new Date(Long.parseLong("2559397136000")); + + private static final Date IAT_VALID_DATE = new Date(Long.parseLong("1707496483000")); + + Wallet wallet = buildWallet(BPN_1, DID_BPN_1, DID_JSON_STRING_1); + + @SneakyThrows + @Test + void validateParsingFromJWTinStringFormatToSignedJWT() { + String siToken = generateSiToken(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, + READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); + SignedJWT accessToken = getAccessToken(siToken); + Assertions.assertNotNull(accessToken); + Assertions.assertEquals(DID_BPN_1, accessToken.getJWTClaimsSet().getIssuer()); + } + + @SneakyThrows + @Test + void createPresentation200ResponseAsJWT() { + boolean asJwt = true; + String siToken = generateSiToken(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, + READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); + SignedJWT accessToken = storeDataAndGetSignedJWT(siToken); + Map presentation = presentationService.createVpWithRequiredScopes(accessToken, asJwt); + cleanData(); + String vpAsJwt = String.valueOf(presentation.get(VERIFIABLE_PRESENTATION)); + JWT jwt = JWTParser.parse(vpAsJwt); + Assertions.assertNotNull(presentation); + Assertions.assertEquals(DID_BPN_1, jwt.getJWTClaimsSet().getSubject()); + Assertions.assertEquals(DID_BPN_1, jwt.getJWTClaimsSet().getIssuer()); + } + + @SneakyThrows + @Test + void createPresentation200ResponseAsJsonLD() { + boolean asJwt = false; + String siToken = generateSiToken(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, + READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); + SignedJWT accessToken = storeDataAndGetSignedJWT(siToken); + Map presentation = presentationService.createVpWithRequiredScopes(accessToken, asJwt); + cleanData(); + Assertions.assertNotNull(presentation); + VerifiablePresentation vp = (VerifiablePresentation) presentation.get(VERIFIABLE_PRESENTATION); + Assertions.assertNotNull(vp.getVerifiableCredentials()); + VerifiableCredential verifiableCredential = vp.getVerifiableCredentials().get(0); + Assertions.assertEquals(DID_BPN_1, verifiableCredential.getIssuer().toString()); + VerifiableCredentialSubject verifiableCredentialSubject = verifiableCredential.getCredentialSubject().get(0); + Assertions.assertNotNull(verifiableCredentialSubject); + Assertions.assertEquals(BPN_1, verifiableCredentialSubject.get("bpn")); + } + + @SneakyThrows + @Test + void createPresentationIncorrectVcTypeResponse() { + boolean asJwt = true; + String siToken = generateSiToken(DID_BPN_2, DID_BPN_2, DID_BPN_2, "12345", + READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); + SignedJWT accessToken = storeDataAndGetSignedJWT(siToken); + Assertions.assertThrows(MissingVcTypesException.class, () -> presentationService.createVpWithRequiredScopes(accessToken, asJwt)); + cleanData(); + } + + @SneakyThrows + @Test + void createPresentationIncorrectRightsRequested() { + boolean asJwt = true; + String siToken = generateSiToken(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, + WRITE_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); + SignedJWT accessToken = storeDataAndGetSignedJWT(siToken); + Assertions.assertThrows(PermissionViolationException.class, () -> presentationService.createVpWithRequiredScopes(accessToken, asJwt)); + cleanData(); + } + + private void cleanData() { + walletRepository.deleteById(wallet.getId()); + holdersCredentialRepository.deleteAll(); + walletKeyService.delete(2L); + } + + private SignedJWT storeDataAndGetSignedJWT(String siToken) throws KeyGenerationException { + List types = List.of(VerifiableCredentialType.VERIFIABLE_CREDENTIAL, MIWVerifiableCredentialType.BPN_CREDENTIAL); + VerifiableCredentialSubject verifiableCredentialSubject = new VerifiableCredentialSubject(Map.of( + StringPool.TYPE, MIWVerifiableCredentialType.BPN_CREDENTIAL, + StringPool.ID, DID_BPN_1, + StringPool.BPN, BPN_1)); + + //create private key pair + IKeyGenerator keyGenerator = new x21559Generator(); + KeyPair keyPair = keyGenerator.generateKey(); + + walletRepository.save(wallet); + WalletKey walletKey = generateWalletKey(keyPair, wallet); + walletKeyService.create(walletKey); + byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(wallet.getId()); + HoldersCredential holdersCredential = CommonUtils.getHoldersCredential(verifiableCredentialSubject, + types, wallet.getDidDocument(), privateKeyBytes, DID_BPN_1, miwSettings.vcContexts(), miwSettings.vcExpiryDate(), + true); + + SignedJWT accessToken = getAccessToken(siToken); + holdersCredentialRepository.save(holdersCredential); + return accessToken; + } + + private WalletKey generateWalletKey(KeyPair keyPair, Wallet wallet) { + return WalletKey.builder() + .id(2L) + .keyId(UUID.randomUUID().toString()) + .privateKey(encryptionUtils.encrypt(getPrivateKeyString(keyPair.getPrivateKey().asByte()))) + .publicKey(encryptionUtils.encrypt(getPublicKeyString(keyPair.getPublicKey().asByte()))) + .referenceKey("dummy ref key") + .wallet(wallet) + .vaultAccessToken("dummy vault access token") + .build(); + } + + @SneakyThrows + private String getPublicKeyString(byte[] publicKeyBytes) { + StringWriter stringWriter = new StringWriter(); + PemWriter pemWriter = new PemWriter(stringWriter); + pemWriter.writeObject(new PemObject("PUBLIC KEY", publicKeyBytes)); + pemWriter.flush(); + pemWriter.close(); + return stringWriter.toString(); + } + + @SneakyThrows + private String getPrivateKeyString(byte[] privateKeyBytes) { + StringWriter stringWriter = new StringWriter(); + PemWriter pemWriter = new PemWriter(stringWriter); + pemWriter.writeObject(new PemObject("PRIVATE KEY", privateKeyBytes)); + pemWriter.flush(); + pemWriter.close(); + return stringWriter.toString(); + } + + private String generateSiToken(String issUrl, String sub, String aud, String nonce, String scope, Date expDate, Date issDate) throws JOSEException { + JWTClaimsSet innerSet = buildClaimsSet(issUrl, sub, aud, nonce, scope, expDate, issDate); + String accessToken = buildJWTToken(JWK_INNER, innerSet); + + JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, "", + EXP_VALID_DATE, IAT_VALID_DATE); + JWTClaimsSet outerSetFull = addAccessTokenToClaimsSet(accessToken, outerSet); + return buildJWTToken(JWK_OUTER, outerSetFull); + } +} From ffce5ba31da90c2acff284ce278e5484f9a66bfb Mon Sep 17 00:00:00 2001 From: andreibogus Date: Thu, 29 Feb 2024 19:05:29 +0100 Subject: [PATCH 12/13] chore: refactor and fix new tests --- .../STSTokenValidationServiceTest.java | 37 ++-- .../utils/TestConstants.java | 25 ++- .../vp/PresentationServiceTest.java | 198 ++++-------------- 3 files changed, 79 insertions(+), 181 deletions(-) diff --git a/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationServiceTest.java b/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationServiceTest.java index 9ad6554a7..d2fd4b3e2 100644 --- a/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationServiceTest.java +++ b/src/test/java/org/eclipse/tractusx/managedidentitywallets/service/STSTokenValidationServiceTest.java @@ -45,15 +45,18 @@ import java.util.Date; -import static com.nimbusds.jose.jwk.Curve.Ed25519; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.BPN_1; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.BPN_2; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.BPN_CREDENTIAL_READ; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_BPN_1; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_BPN_2; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_JSON_STRING_1; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_JSON_STRING_2; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.EXP_VALID_DATE; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.IAT_VALID_DATE; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.JWK_INNER; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.JWK_OUTER; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.NONCE; -import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.READ_SCOPE; import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.addAccessTokenToClaimsSet; import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildClaimsSet; import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildJWTToken; @@ -63,24 +66,8 @@ @ContextConfiguration(initializers = { TestContextInitializer.class }) class STSTokenValidationServiceTest { - private static final OctetKeyPair JWK_OUTER = new OctetKeyPair - .Builder(Ed25519, new Base64URL("4Q5HCXPyutfcj7gLmbAKlYttlJPkykIkRjh7DH2NtZ0")) - .d(new Base64URL("Ktp0sv9dKr_gnzRxpH5V9qpiTgZ1WbkMSv8WtWodewg")) - .keyID("58cb4b32-c2e4-46f0-a3ad-3286e34765ed") - .build(); - - private static final OctetKeyPair JWK_INNER = new OctetKeyPair - .Builder(Ed25519, new Base64URL("Z-8DEkN6pw2E01niDWqrp1kROLF-syIPIpFgmyrVUOU")) - .d(new Base64URL("MLYxSai_oFzuqEfnB2diA3oDuixLg3kQzZKMyW31-2o")) - .keyID("58cb4b32-c2e4-46f0-a3ad-3286e34765ty") - .build(); - - private static final Date EXP_VALID_DATE = new Date(Long.parseLong("2559397136000")); - private static final Date ALREADY_EXP_DATE = new Date(Long.parseLong("1707582883000")); - private static final Date IAT_VALID_DATE = new Date(Long.parseLong("1707496483000")); - @Autowired private STSTokenValidationService stsTokenValidationService; @@ -117,7 +104,7 @@ public void cleanWallets() { @Test void validateTokenFailureAccessTokenMissingTest() throws JOSEException { - JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); + JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, BPN_CREDENTIAL_READ, EXP_VALID_DATE, IAT_VALID_DATE); String siToken = buildJWTToken(JWK_OUTER, outerSet); ValidationResult result = stsTokenValidationService.validateToken(siToken); @@ -133,10 +120,10 @@ void validateTokenFailureWrongSignatureInnerTokenTest() throws JOSEException { .keyID("58cb4b32-c2e4-46f0-a3ad-3286e34765ty") .generate(); - JWTClaimsSet innerSet = buildClaimsSet(DID_BPN_2, DID_BPN_1, DID_BPN_1, NONCE, READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); + JWTClaimsSet innerSet = buildClaimsSet(DID_BPN_2, DID_BPN_1, DID_BPN_1, NONCE, BPN_CREDENTIAL_READ, EXP_VALID_DATE, IAT_VALID_DATE); String accessToken = buildJWTToken(jwkRandom, innerSet); - JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, READ_SCOPE, EXP_VALID_DATE, ALREADY_EXP_DATE); + JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, BPN_CREDENTIAL_READ, EXP_VALID_DATE, ALREADY_EXP_DATE); JWTClaimsSet outerSetFull = addAccessTokenToClaimsSet(accessToken, outerSet); String siToken = buildJWTToken(JWK_OUTER, outerSetFull); @@ -148,10 +135,10 @@ void validateTokenFailureWrongSignatureInnerTokenTest() throws JOSEException { @Test void validateTokenFailureExpiredTokenIssNotEqualsSubTest() throws JOSEException { - JWTClaimsSet innerSet = buildClaimsSet(DID_BPN_2, DID_BPN_1, DID_BPN_1, NONCE, READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); + JWTClaimsSet innerSet = buildClaimsSet(DID_BPN_2, DID_BPN_1, DID_BPN_1, NONCE, BPN_CREDENTIAL_READ, EXP_VALID_DATE, IAT_VALID_DATE); String accessToken = buildJWTToken(JWK_INNER, innerSet); - JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_2, DID_BPN_1, NONCE, READ_SCOPE, ALREADY_EXP_DATE, IAT_VALID_DATE); + JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_2, DID_BPN_1, NONCE, BPN_CREDENTIAL_READ, ALREADY_EXP_DATE, IAT_VALID_DATE); JWTClaimsSet outerSetFull = addAccessTokenToClaimsSet(accessToken, outerSet); String siToken = buildJWTToken(JWK_OUTER, outerSetFull); @@ -164,10 +151,10 @@ void validateTokenFailureExpiredTokenIssNotEqualsSubTest() throws JOSEException @Test void validateTokenSuccessTest() throws JOSEException { - JWTClaimsSet innerSet = buildClaimsSet(DID_BPN_2, DID_BPN_1, DID_BPN_1, NONCE, READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); + JWTClaimsSet innerSet = buildClaimsSet(DID_BPN_2, DID_BPN_1, DID_BPN_1, NONCE, BPN_CREDENTIAL_READ, EXP_VALID_DATE, IAT_VALID_DATE); String accessToken = buildJWTToken(JWK_INNER, innerSet); - JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); + JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, BPN_CREDENTIAL_READ, EXP_VALID_DATE, IAT_VALID_DATE); JWTClaimsSet outerSetFull = addAccessTokenToClaimsSet(accessToken, outerSet); String siToken = buildJWTToken(JWK_OUTER, outerSetFull); diff --git a/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestConstants.java b/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestConstants.java index 36eb0feaa..59380f2d2 100644 --- a/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestConstants.java +++ b/src/test/java/org/eclipse/tractusx/managedidentitywallets/utils/TestConstants.java @@ -21,16 +21,37 @@ package org.eclipse.tractusx.managedidentitywallets.utils; +import com.nimbusds.jose.jwk.OctetKeyPair; +import com.nimbusds.jose.util.Base64URL; + +import java.util.Date; + +import static com.nimbusds.jose.jwk.Curve.Ed25519; + public class TestConstants { public static final String DID_BPN_1 = "did:web:localhost:BPNL000000000001"; public static final String DID_BPN_2 = "did:web:localhost:BPNL000000000002"; public static final String BPN_1 = "BPNL000000000001"; public static final String BPN_2 = "BPNL000000000002"; - public static final String READ_SCOPE = "org.eclipse.tractusx.vc.type:BpnCredential:read"; - public static final String WRITE_SCOPE = "org.eclipse.tractusx.vc.type:BpnCredential:write"; + public static final String BPN_CREDENTIAL_READ = "org.eclipse.tractusx.vc.type:BpnCredential:read"; + public static final String INVALID_CREDENTIAL_READ = "org.eclipse.tractusx.vc.type:InvalidCredential:read"; + public static final String BPN_CREDENTIAL_WRITE = "org.eclipse.tractusx.vc.type:BpnCredential:write"; public static final String VERIFIABLE_PRESENTATION = "vp"; public static final String NONCE = "123456"; + public static final OctetKeyPair JWK_OUTER = new OctetKeyPair + .Builder(Ed25519, new Base64URL("4Q5HCXPyutfcj7gLmbAKlYttlJPkykIkRjh7DH2NtZ0")) + .d(new Base64URL("Ktp0sv9dKr_gnzRxpH5V9qpiTgZ1WbkMSv8WtWodewg")) + .keyID("58cb4b32-c2e4-46f0-a3ad-3286e34765ed") + .build(); + + public static final OctetKeyPair JWK_INNER = new OctetKeyPair + .Builder(Ed25519, new Base64URL("Z-8DEkN6pw2E01niDWqrp1kROLF-syIPIpFgmyrVUOU")) + .d(new Base64URL("MLYxSai_oFzuqEfnB2diA3oDuixLg3kQzZKMyW31-2o")) + .keyID("58cb4b32-c2e4-46f0-a3ad-3286e34765ty") + .build(); + public static final Date EXP_VALID_DATE = new Date(Long.parseLong("2559397136000")); + public static final Date IAT_VALID_DATE = new Date(Long.parseLong("1707496483000")); public static final String DID_JSON_STRING_1 = """ { "@context": [ diff --git a/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java b/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java index 598b03bfa..0d986cfe1 100644 --- a/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java +++ b/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java @@ -22,81 +22,50 @@ package org.eclipse.tractusx.managedidentitywallets.vp; import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.jwk.OctetKeyPair; -import com.nimbusds.jose.util.Base64URL; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.JWTParser; import com.nimbusds.jwt.SignedJWT; import lombok.SneakyThrows; -import org.bouncycastle.util.io.pem.PemObject; -import org.bouncycastle.util.io.pem.PemWriter; 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.MIWVerifiableCredentialType; -import org.eclipse.tractusx.managedidentitywallets.constant.StringPool; -import org.eclipse.tractusx.managedidentitywallets.dao.entity.HoldersCredential; import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet; -import org.eclipse.tractusx.managedidentitywallets.dao.entity.WalletKey; -import org.eclipse.tractusx.managedidentitywallets.dao.repository.HoldersCredentialRepository; -import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletRepository; import org.eclipse.tractusx.managedidentitywallets.exception.MissingVcTypesException; import org.eclipse.tractusx.managedidentitywallets.exception.PermissionViolationException; import org.eclipse.tractusx.managedidentitywallets.service.PresentationService; -import org.eclipse.tractusx.managedidentitywallets.service.WalletKeyService; -import org.eclipse.tractusx.managedidentitywallets.utils.CommonUtils; -import org.eclipse.tractusx.managedidentitywallets.utils.EncryptionUtils; -import org.eclipse.tractusx.ssi.lib.crypt.IKeyGenerator; -import org.eclipse.tractusx.ssi.lib.crypt.KeyPair; -import org.eclipse.tractusx.ssi.lib.crypt.x21559.x21559Generator; -import org.eclipse.tractusx.ssi.lib.exception.KeyGenerationException; +import org.eclipse.tractusx.managedidentitywallets.utils.TestUtils; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialSubject; -import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialType; import org.eclipse.tractusx.ssi.lib.model.verifiable.presentation.VerifiablePresentation; import org.junit.jupiter.api.Assertions; 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.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; -import java.io.StringWriter; import java.util.Date; -import java.util.List; import java.util.Map; -import java.util.UUID; -import static com.nimbusds.jose.jwk.Curve.Ed25519; -import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.BPN_1; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.BPN_CREDENTIAL_READ; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.BPN_CREDENTIAL_WRITE; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_BPN_1; -import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_BPN_2; -import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_JSON_STRING_1; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.EXP_VALID_DATE; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.IAT_VALID_DATE; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.INVALID_CREDENTIAL_READ; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.JWK_INNER; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.NONCE; -import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.READ_SCOPE; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.VERIFIABLE_PRESENTATION; -import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.WRITE_SCOPE; -import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.addAccessTokenToClaimsSet; import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildClaimsSet; import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildJWTToken; -import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildWallet; -import static org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils.getAccessToken; +import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.createWallet; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class }) @ContextConfiguration(initializers = { TestContextInitializer.class }) public class PresentationServiceTest { - @Autowired - private HoldersCredentialRepository holdersCredentialRepository; - - @Autowired - private WalletRepository walletRepository; - - @Autowired - private WalletKeyService walletKeyService; - - @Autowired - private EncryptionUtils encryptionUtils; @Autowired private MIWSettings miwSettings; @@ -104,160 +73,81 @@ public class PresentationServiceTest { @Autowired private PresentationService presentationService; - private static final OctetKeyPair JWK_OUTER = new OctetKeyPair - .Builder(Ed25519, new Base64URL("4Q5HCXPyutfcj7gLmbAKlYttlJPkykIkRjh7DH2NtZ0")) - .d(new Base64URL("Ktp0sv9dKr_gnzRxpH5V9qpiTgZ1WbkMSv8WtWodewg")) - .keyID("58cb4b32-c2e4-46f0-a3ad-3286e34765ed") - .build(); - - private static final OctetKeyPair JWK_INNER = new OctetKeyPair - .Builder(Ed25519, new Base64URL("Z-8DEkN6pw2E01niDWqrp1kROLF-syIPIpFgmyrVUOU")) - .d(new Base64URL("MLYxSai_oFzuqEfnB2diA3oDuixLg3kQzZKMyW31-2o")) - .keyID("58cb4b32-c2e4-46f0-a3ad-3286e34765ty") - .build(); - - private static final Date EXP_VALID_DATE = new Date(Long.parseLong("2559397136000")); - - private static final Date IAT_VALID_DATE = new Date(Long.parseLong("1707496483000")); - - Wallet wallet = buildWallet(BPN_1, DID_BPN_1, DID_JSON_STRING_1); + @Autowired + private TestRestTemplate restTemplate; @SneakyThrows - @Test - void validateParsingFromJWTinStringFormatToSignedJWT() { - String siToken = generateSiToken(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, - READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); - SignedJWT accessToken = getAccessToken(siToken); - Assertions.assertNotNull(accessToken); - Assertions.assertEquals(DID_BPN_1, accessToken.getJWTClaimsSet().getIssuer()); + public String generateWalletAndSetDid(String bpn) { + Wallet wallet = generateWallet(bpn); + return wallet.getDid(); } @SneakyThrows @Test void createPresentation200ResponseAsJWT() { boolean asJwt = true; - String siToken = generateSiToken(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, - READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); - SignedJWT accessToken = storeDataAndGetSignedJWT(siToken); - Map presentation = presentationService.createVpWithRequiredScopes(accessToken, asJwt); - cleanData(); + String bpn = TestUtils.getRandomBpmNumber(); + String did = generateWalletAndSetDid(bpn); + String accessToken = generateAccessToken(did, did, did, NONCE, BPN_CREDENTIAL_READ, EXP_VALID_DATE, IAT_VALID_DATE); + Map presentation = presentationService.createVpWithRequiredScopes(SignedJWT.parse(accessToken), asJwt); String vpAsJwt = String.valueOf(presentation.get(VERIFIABLE_PRESENTATION)); JWT jwt = JWTParser.parse(vpAsJwt); Assertions.assertNotNull(presentation); - Assertions.assertEquals(DID_BPN_1, jwt.getJWTClaimsSet().getSubject()); - Assertions.assertEquals(DID_BPN_1, jwt.getJWTClaimsSet().getIssuer()); + Assertions.assertEquals(did, jwt.getJWTClaimsSet().getSubject()); + Assertions.assertEquals(did, jwt.getJWTClaimsSet().getIssuer()); } @SneakyThrows @Test void createPresentation200ResponseAsJsonLD() { boolean asJwt = false; - String siToken = generateSiToken(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, - READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); - SignedJWT accessToken = storeDataAndGetSignedJWT(siToken); - Map presentation = presentationService.createVpWithRequiredScopes(accessToken, asJwt); - cleanData(); + String bpn = TestUtils.getRandomBpmNumber(); + String did = generateWalletAndSetDid(bpn); + String accessToken = generateAccessToken(did, did, did, NONCE, + BPN_CREDENTIAL_READ, EXP_VALID_DATE, IAT_VALID_DATE); + Map presentation = presentationService.createVpWithRequiredScopes(SignedJWT.parse(accessToken), asJwt); Assertions.assertNotNull(presentation); VerifiablePresentation vp = (VerifiablePresentation) presentation.get(VERIFIABLE_PRESENTATION); Assertions.assertNotNull(vp.getVerifiableCredentials()); VerifiableCredential verifiableCredential = vp.getVerifiableCredentials().get(0); - Assertions.assertEquals(DID_BPN_1, verifiableCredential.getIssuer().toString()); VerifiableCredentialSubject verifiableCredentialSubject = verifiableCredential.getCredentialSubject().get(0); Assertions.assertNotNull(verifiableCredentialSubject); - Assertions.assertEquals(BPN_1, verifiableCredentialSubject.get("bpn")); + Assertions.assertEquals(bpn, verifiableCredentialSubject.get("bpn")); + Assertions.assertEquals(did, verifiableCredentialSubject.get("id")); } @SneakyThrows @Test void createPresentationIncorrectVcTypeResponse() { boolean asJwt = true; - String siToken = generateSiToken(DID_BPN_2, DID_BPN_2, DID_BPN_2, "12345", - READ_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); - SignedJWT accessToken = storeDataAndGetSignedJWT(siToken); - Assertions.assertThrows(MissingVcTypesException.class, () -> presentationService.createVpWithRequiredScopes(accessToken, asJwt)); - cleanData(); + String bpn = TestUtils.getRandomBpmNumber(); + String did = generateWalletAndSetDid(bpn); + String accessToken = generateAccessToken(did, did, did, "12345", + INVALID_CREDENTIAL_READ, EXP_VALID_DATE, IAT_VALID_DATE); + Assertions.assertThrows(MissingVcTypesException.class, () -> + presentationService.createVpWithRequiredScopes(SignedJWT.parse(accessToken), asJwt)); } @SneakyThrows @Test void createPresentationIncorrectRightsRequested() { boolean asJwt = true; - String siToken = generateSiToken(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, - WRITE_SCOPE, EXP_VALID_DATE, IAT_VALID_DATE); - SignedJWT accessToken = storeDataAndGetSignedJWT(siToken); - Assertions.assertThrows(PermissionViolationException.class, () -> presentationService.createVpWithRequiredScopes(accessToken, asJwt)); - cleanData(); - } - - private void cleanData() { - walletRepository.deleteById(wallet.getId()); - holdersCredentialRepository.deleteAll(); - walletKeyService.delete(2L); - } - - private SignedJWT storeDataAndGetSignedJWT(String siToken) throws KeyGenerationException { - List types = List.of(VerifiableCredentialType.VERIFIABLE_CREDENTIAL, MIWVerifiableCredentialType.BPN_CREDENTIAL); - VerifiableCredentialSubject verifiableCredentialSubject = new VerifiableCredentialSubject(Map.of( - StringPool.TYPE, MIWVerifiableCredentialType.BPN_CREDENTIAL, - StringPool.ID, DID_BPN_1, - StringPool.BPN, BPN_1)); - - //create private key pair - IKeyGenerator keyGenerator = new x21559Generator(); - KeyPair keyPair = keyGenerator.generateKey(); - - walletRepository.save(wallet); - WalletKey walletKey = generateWalletKey(keyPair, wallet); - walletKeyService.create(walletKey); - byte[] privateKeyBytes = walletKeyService.getPrivateKeyByWalletIdentifierAsBytes(wallet.getId()); - HoldersCredential holdersCredential = CommonUtils.getHoldersCredential(verifiableCredentialSubject, - types, wallet.getDidDocument(), privateKeyBytes, DID_BPN_1, miwSettings.vcContexts(), miwSettings.vcExpiryDate(), - true); - - SignedJWT accessToken = getAccessToken(siToken); - holdersCredentialRepository.save(holdersCredential); - return accessToken; - } - - private WalletKey generateWalletKey(KeyPair keyPair, Wallet wallet) { - return WalletKey.builder() - .id(2L) - .keyId(UUID.randomUUID().toString()) - .privateKey(encryptionUtils.encrypt(getPrivateKeyString(keyPair.getPrivateKey().asByte()))) - .publicKey(encryptionUtils.encrypt(getPublicKeyString(keyPair.getPublicKey().asByte()))) - .referenceKey("dummy ref key") - .wallet(wallet) - .vaultAccessToken("dummy vault access token") - .build(); + String accessToken = generateAccessToken(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, + BPN_CREDENTIAL_WRITE, EXP_VALID_DATE, IAT_VALID_DATE); + Assertions.assertThrows(PermissionViolationException.class, () -> + presentationService.createVpWithRequiredScopes(SignedJWT.parse(accessToken), asJwt)); } @SneakyThrows - private String getPublicKeyString(byte[] publicKeyBytes) { - StringWriter stringWriter = new StringWriter(); - PemWriter pemWriter = new PemWriter(stringWriter); - pemWriter.writeObject(new PemObject("PUBLIC KEY", publicKeyBytes)); - pemWriter.flush(); - pemWriter.close(); - return stringWriter.toString(); + private Wallet generateWallet(String bpn) { + String baseBpn = miwSettings.authorityWalletBpn(); + ResponseEntity createWalletResponse = createWallet(bpn, "name", restTemplate, baseBpn); + return TestUtils.getWalletFromString(createWalletResponse.getBody()); } - @SneakyThrows - private String getPrivateKeyString(byte[] privateKeyBytes) { - StringWriter stringWriter = new StringWriter(); - PemWriter pemWriter = new PemWriter(stringWriter); - pemWriter.writeObject(new PemObject("PRIVATE KEY", privateKeyBytes)); - pemWriter.flush(); - pemWriter.close(); - return stringWriter.toString(); - } - private String generateSiToken(String issUrl, String sub, String aud, String nonce, String scope, Date expDate, Date issDate) throws JOSEException { + private String generateAccessToken(String issUrl, String sub, String aud, String nonce, String scope, Date expDate, Date issDate) throws JOSEException { JWTClaimsSet innerSet = buildClaimsSet(issUrl, sub, aud, nonce, scope, expDate, issDate); - String accessToken = buildJWTToken(JWK_INNER, innerSet); - - JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, "", - EXP_VALID_DATE, IAT_VALID_DATE); - JWTClaimsSet outerSetFull = addAccessTokenToClaimsSet(accessToken, outerSet); - return buildJWTToken(JWK_OUTER, outerSetFull); + return buildJWTToken(JWK_INNER, innerSet); } } From 4b3c7644a955d65462be628381fddf71e5bf25ec Mon Sep 17 00:00:00 2001 From: aleksandra-bel Date: Fri, 1 Mar 2024 09:41:31 +0100 Subject: [PATCH 13/13] chore: refactor test --- .../vp/PresentationServiceTest.java | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java b/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java index 0d986cfe1..bbff24bfa 100644 --- a/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java +++ b/src/test/java/org/eclipse/tractusx/managedidentitywallets/vp/PresentationServiceTest.java @@ -34,6 +34,7 @@ import org.eclipse.tractusx.managedidentitywallets.exception.MissingVcTypesException; import org.eclipse.tractusx.managedidentitywallets.exception.PermissionViolationException; import org.eclipse.tractusx.managedidentitywallets.service.PresentationService; +import org.eclipse.tractusx.managedidentitywallets.utils.TestConstants; import org.eclipse.tractusx.managedidentitywallets.utils.TestUtils; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredential; import org.eclipse.tractusx.ssi.lib.model.verifiable.credential.VerifiableCredentialSubject; @@ -46,7 +47,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.test.context.ContextConfiguration; -import java.util.Date; import java.util.Map; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.BPN_CREDENTIAL_READ; @@ -56,7 +56,6 @@ import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.IAT_VALID_DATE; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.INVALID_CREDENTIAL_READ; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.JWK_INNER; -import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.NONCE; import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.VERIFIABLE_PRESENTATION; import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildClaimsSet; import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildJWTToken; @@ -76,22 +75,18 @@ public class PresentationServiceTest { @Autowired private TestRestTemplate restTemplate; - @SneakyThrows - public String generateWalletAndSetDid(String bpn) { - Wallet wallet = generateWallet(bpn); - return wallet.getDid(); - } - @SneakyThrows @Test void createPresentation200ResponseAsJWT() { boolean asJwt = true; String bpn = TestUtils.getRandomBpmNumber(); - String did = generateWalletAndSetDid(bpn); - String accessToken = generateAccessToken(did, did, did, NONCE, BPN_CREDENTIAL_READ, EXP_VALID_DATE, IAT_VALID_DATE); + String did = generateWalletAndGetDid(bpn); + String accessToken = generateAccessToken(did, did, did, BPN_CREDENTIAL_READ); + Map presentation = presentationService.createVpWithRequiredScopes(SignedJWT.parse(accessToken), asJwt); String vpAsJwt = String.valueOf(presentation.get(VERIFIABLE_PRESENTATION)); JWT jwt = JWTParser.parse(vpAsJwt); + Assertions.assertNotNull(presentation); Assertions.assertEquals(did, jwt.getJWTClaimsSet().getSubject()); Assertions.assertEquals(did, jwt.getJWTClaimsSet().getIssuer()); @@ -102,11 +97,12 @@ void createPresentation200ResponseAsJWT() { void createPresentation200ResponseAsJsonLD() { boolean asJwt = false; String bpn = TestUtils.getRandomBpmNumber(); - String did = generateWalletAndSetDid(bpn); - String accessToken = generateAccessToken(did, did, did, NONCE, - BPN_CREDENTIAL_READ, EXP_VALID_DATE, IAT_VALID_DATE); + String did = generateWalletAndGetDid(bpn); + String accessToken = generateAccessToken(did, did, did, BPN_CREDENTIAL_READ); + Map presentation = presentationService.createVpWithRequiredScopes(SignedJWT.parse(accessToken), asJwt); Assertions.assertNotNull(presentation); + VerifiablePresentation vp = (VerifiablePresentation) presentation.get(VERIFIABLE_PRESENTATION); Assertions.assertNotNull(vp.getVerifiableCredentials()); VerifiableCredential verifiableCredential = vp.getVerifiableCredentials().get(0); @@ -121,9 +117,9 @@ void createPresentation200ResponseAsJsonLD() { void createPresentationIncorrectVcTypeResponse() { boolean asJwt = true; String bpn = TestUtils.getRandomBpmNumber(); - String did = generateWalletAndSetDid(bpn); - String accessToken = generateAccessToken(did, did, did, "12345", - INVALID_CREDENTIAL_READ, EXP_VALID_DATE, IAT_VALID_DATE); + String did = generateWalletAndGetDid(bpn); + String accessToken = generateAccessToken(did, did, did, INVALID_CREDENTIAL_READ); + Assertions.assertThrows(MissingVcTypesException.class, () -> presentationService.createVpWithRequiredScopes(SignedJWT.parse(accessToken), asJwt)); } @@ -132,22 +128,22 @@ void createPresentationIncorrectVcTypeResponse() { @Test void createPresentationIncorrectRightsRequested() { boolean asJwt = true; - String accessToken = generateAccessToken(DID_BPN_1, DID_BPN_1, DID_BPN_1, NONCE, - BPN_CREDENTIAL_WRITE, EXP_VALID_DATE, IAT_VALID_DATE); + String accessToken = generateAccessToken(DID_BPN_1, DID_BPN_1, DID_BPN_1, BPN_CREDENTIAL_WRITE); + Assertions.assertThrows(PermissionViolationException.class, () -> presentationService.createVpWithRequiredScopes(SignedJWT.parse(accessToken), asJwt)); } @SneakyThrows - private Wallet generateWallet(String bpn) { + private String generateWalletAndGetDid(String bpn) { String baseBpn = miwSettings.authorityWalletBpn(); ResponseEntity createWalletResponse = createWallet(bpn, "name", restTemplate, baseBpn); - return TestUtils.getWalletFromString(createWalletResponse.getBody()); + Wallet wallet = TestUtils.getWalletFromString(createWalletResponse.getBody()); + return wallet.getDid(); } - - private String generateAccessToken(String issUrl, String sub, String aud, String nonce, String scope, Date expDate, Date issDate) throws JOSEException { - JWTClaimsSet innerSet = buildClaimsSet(issUrl, sub, aud, nonce, scope, expDate, issDate); + private String generateAccessToken(String issUrl, String sub, String aud, String scope) throws JOSEException { + JWTClaimsSet innerSet = buildClaimsSet(issUrl, sub, aud, TestConstants.NONCE, scope, EXP_VALID_DATE, IAT_VALID_DATE); return buildJWTToken(JWK_INNER, innerSet); } }