Skip to content

Commit

Permalink
feat: add service method, controller, config for scope matching
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksandra-bel authored and andreibogus committed Feb 27, 2024
1 parent 0232cd9 commit 4feebd4
Show file tree
Hide file tree
Showing 8 changed files with 310 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -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<TokenValidationErrors> 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);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -51,6 +54,9 @@
@AllArgsConstructor
public class SecurityConfig {

@Autowired
private final STSTokenValidationService validationService;

private final SecurityConfigProperties securityConfigProperties;

/**
Expand All @@ -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
Expand Down Expand Up @@ -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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,30 @@

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;
import org.eclipse.tractusx.managedidentitywallets.service.PresentationService;
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.
*/
Expand Down Expand Up @@ -90,4 +95,19 @@ public ResponseEntity<Map<String, Object>> 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<Map<String, Object>> createPresentation(@RequestHeader(name = "Authorization") String stsToken) {
SignedJWT accessToken = getAccessToken(stsToken);
Map<String, Object> vp = presentationService.createVpWithRequiredScopes(accessToken);
return ResponseEntity.ok(vp);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -108,7 +112,6 @@ protected SpecificationUtil<HoldersCredential> getSpecificationUtil() {
* @param callerBpn the caller bpn
* @return the map
*/
@SneakyThrows({InvalidePrivateKeyFormat.class})
public Map<String, Object> createPresentation(Map<String, Object> data, boolean asJwt, String audience, String callerBpn) {
List<Map<String, Object>> verifiableCredentialList = (List<Map<String, Object>>) data.get(StringPool.VERIFIABLE_CREDENTIALS);

Expand All @@ -121,6 +124,11 @@ public Map<String, Object> createPresentation(Map<String, Object> data, boolean
verifiableCredentials.add(verifiableCredential);
});

return buildVP(asJwt, audience, callerBpn, callerWallet, verifiableCredentials);
}

@SneakyThrows({ InvalidePrivateKeyFormat.class })
private Map<String, Object> buildVP(boolean asJwt, String audience, String callerBpn, Wallet callerWallet, List<VerifiableCredential> verifiableCredentials) {
Map<String, Object> response = new HashMap<>();
if (asJwt) {
log.debug("Creating VP as JWT for bpn ->{}", callerBpn);
Expand Down Expand Up @@ -276,4 +284,49 @@ private boolean validateCredential(VerifiableCredential credential) {
}
return isValid;
}

public Map<String, Object> createVpWithRequiredScopes(SignedJWT innerJWT) {
List<HoldersCredential> holdersCredentials = new ArrayList<>();
List<String> missingVCTypes = new ArrayList<>();
List<VerifiableCredential> 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<HoldersCredential> 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<String> missingVCTypes) {
if (!missingVCTypes.isEmpty()) {
throw new MissingVcTypesException(String.format("MissingVCs of types: %s", missingVCTypes));
}
}
}
Loading

0 comments on commit 4feebd4

Please sign in to comment.