Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: bpn auth validation #328

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* *******************************************************************************
* 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 jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Setter;
import org.eclipse.tractusx.managedidentitywallets.constant.StringPool;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.util.StringUtils;

import java.util.LinkedHashMap;
import java.util.Map;

/**
* The type Custom authentication entry point.
*/
@Setter
public final class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

private String realmName;

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
HttpStatus status = HttpStatus.UNAUTHORIZED;
Map<String, String> parameters = new LinkedHashMap<>();

if (this.realmName != null) {
parameters.put("realm", this.realmName);
}

if (authException instanceof OAuth2AuthenticationException) {
OAuth2Error error = ((OAuth2AuthenticationException) authException).getError();
parameters.put("error", error.getErrorCode());
if (StringUtils.hasText(error.getDescription())) {
parameters.put("error_description", error.getDescription());
}

if (StringUtils.hasText(error.getUri())) {
parameters.put("error_uri", error.getUri());
}

if (error instanceof BearerTokenError bearerTokenError) {
if (StringUtils.hasText(bearerTokenError.getScope())) {
parameters.put("scope", bearerTokenError.getScope());
}

status = ((BearerTokenError) error).getHttpStatus();
}
}

if (authException.getMessage().contains(StringPool.BPN_NOT_FOUND)) {
status = HttpStatus.FORBIDDEN;
}

String wwwAuthenticate = computeWWWAuthenticateHeaderValue(parameters);
response.addHeader(HttpHeaders.WWW_AUTHENTICATE, wwwAuthenticate);
response.setStatus(status.value());
}

private static String computeWWWAuthenticateHeaderValue(Map<String, String> parameters) {
StringBuilder wwwAuthenticate = new StringBuilder();
wwwAuthenticate.append("Bearer");
if (!parameters.isEmpty()) {
wwwAuthenticate.append(" ");
int i = 0;
for (Map.Entry<String, String> entry : parameters.entrySet()) {
wwwAuthenticate.append(entry.getKey()).append("=\"").append(entry.getValue()).append("\"");
if (i != parameters.size() - 1) {
wwwAuthenticate.append(", ");
}
i++;
}
}
return wwwAuthenticate.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@

package org.eclipse.tractusx.managedidentitywallets.config.security;

import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
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.eclipse.tractusx.managedidentitywallets.utils.BpnValidator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
Expand All @@ -39,6 +41,12 @@
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoders;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
Expand All @@ -53,13 +61,16 @@
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
@Configuration
@AllArgsConstructor
@RequiredArgsConstructor
public class SecurityConfig {

private final STSTokenValidationService validationService;

private final SecurityConfigProperties securityConfigProperties;

@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuerUri;

/**
* Filter chain security filter chain.
*
Expand Down Expand Up @@ -115,7 +126,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()))))
jwt.jwtAuthenticationConverter(new CustomAuthenticationConverter(securityConfigProperties.clientId())))
.authenticationEntryPoint(new CustomAuthenticationEntryPoint()))
.addFilterAfter(new PresentationIatpFilter(validationService), BasicAuthenticationFilter.class);

return http.build();
Expand All @@ -141,4 +153,17 @@ public WebSecurityCustomizer securityCustomizer() {
(ApplicationEventPublisher applicationEventPublisher) {
return new DefaultAuthenticationEventPublisher(applicationEventPublisher);
}

@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(issuerUri);
OAuth2TokenValidator<Jwt> bpnValidator = bpnValidator();
OAuth2TokenValidator<Jwt> withBpn = new DelegatingOAuth2TokenValidator<>(bpnValidator);
jwtDecoder.setJwtValidator(withBpn);
return jwtDecoder;
}

OAuth2TokenValidator<Jwt> bpnValidator() {
return new BpnValidator();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,5 @@ private StringPool() {
public static final String SECURITY_TOKEN_SERVICE = "SecurityTokenService";
public static final String CREDENTIAL_SERVICE = "CredentialService";
public static final String HTTPS_SCHEME = "https://";
public static final String BPN_NOT_FOUND = "BPN not found";
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
@RequiredArgsConstructor
@Tag(name = "DIDDocument")
@Slf4j
public class DidDocumentController extends BaseController {
public class DidDocumentController {
nitin-vavdiya marked this conversation as resolved.
Show resolved Hide resolved
private final DidDocumentService service;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,18 @@
import org.eclipse.tractusx.managedidentitywallets.constant.StringPool;
import org.eclipse.tractusx.managedidentitywallets.dto.CredentialsResponse;
import org.eclipse.tractusx.managedidentitywallets.service.HoldersCredentialService;
import org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils;
import org.springframework.data.domain.PageImpl;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;
import java.util.List;
import java.util.Map;

Expand All @@ -58,7 +59,7 @@
@RequiredArgsConstructor
@Slf4j
@Tag(name = "Verifiable Credential - Holder")
public class HoldersCredentialController extends BaseController {
public class HoldersCredentialController {

private final HoldersCredentialService holdersCredentialService;

Expand All @@ -71,7 +72,7 @@ public class HoldersCredentialController extends BaseController {
* @param type the type
* @param sortColumn the sort column
* @param sortTpe the sort tpe
* @param principal the principal
* @param authentication the authentication
* @return the credentials
*/
@GetCredentialsApiDocs
Expand All @@ -94,8 +95,8 @@ public ResponseEntity<PageImpl<CredentialsResponse>> getCredentials(@Parameter(n
@Min(0) @Max(Integer.MAX_VALUE) @Parameter(description = "Number of records per page") @RequestParam(required = false, defaultValue = Integer.MAX_VALUE + "") int size,
@AsJwtParam @RequestParam(name = StringPool.AS_JWT, defaultValue = "false") boolean asJwt,

Principal principal) {
log.debug("Received request to get credentials. BPN: {}", getBPNFromToken(principal));
Authentication authentication) {
log.debug("Received request to get credentials. BPN: {}", TokenParsingUtils.getBPNFromToken(authentication));
final GetCredentialsCommand command;
command = GetCredentialsCommand.builder()
.credentialId(credentialId)
Expand All @@ -106,7 +107,7 @@ public ResponseEntity<PageImpl<CredentialsResponse>> getCredentials(@Parameter(n
.pageNumber(pageNumber)
.size(size)
.asJwt(asJwt)
.callerBPN(getBPNFromToken(principal))
.callerBPN(TokenParsingUtils.getBPNFromToken(authentication))
.build();
return ResponseEntity.status(HttpStatus.OK).body(holdersCredentialService.getCredentials(command));
}
Expand All @@ -115,17 +116,17 @@ public ResponseEntity<PageImpl<CredentialsResponse>> getCredentials(@Parameter(n
/**
* Issue credential response entity.
*
* @param data the data
* @param principal the principal
* @param data the data
* @param authentication the authentication
* @return the response entity
*/

@IssueCredentialApiDoc
@PostMapping(path = RestURI.CREDENTIALS, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<CredentialsResponse> issueCredential(@RequestBody Map<String, Object> data, Principal principal,
public ResponseEntity<CredentialsResponse> issueCredential(@RequestBody Map<String, Object> data, Authentication authentication,
@AsJwtParam @RequestParam(name = "asJwt", defaultValue = "false") boolean asJwt
) {
log.debug("Received request to issue credential. BPN: {}", getBPNFromToken(principal));
return ResponseEntity.status(HttpStatus.CREATED).body(holdersCredentialService.issueCredential(data, getBPNFromToken(principal), asJwt));
log.debug("Received request to issue credential. BPN: {}", TokenParsingUtils.getBPNFromToken(authentication));
return ResponseEntity.status(HttpStatus.CREATED).body(holdersCredentialService.issueCredential(data, TokenParsingUtils.getBPNFromToken(authentication), asJwt));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,18 @@
import org.eclipse.tractusx.managedidentitywallets.dto.CredentialVerificationRequest;
import org.eclipse.tractusx.managedidentitywallets.dto.CredentialsResponse;
import org.eclipse.tractusx.managedidentitywallets.service.IssuersCredentialService;
import org.eclipse.tractusx.managedidentitywallets.utils.TokenParsingUtils;
import org.springframework.data.domain.PageImpl;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;
import java.util.List;
import java.util.Map;

Expand All @@ -57,7 +58,7 @@
@RestController
@RequiredArgsConstructor
@Slf4j
public class IssuersCredentialController extends BaseController {
public class IssuersCredentialController {

/**
* The constant API_TAG_VERIFIABLE_CREDENTIAL_ISSUER.
Expand All @@ -81,7 +82,7 @@ public class IssuersCredentialController extends BaseController {
* @param size the size
* @param sortColumn the sort column
* @param sortTpe the sort tpe
* @param principal the principal
* @param authentication the authentication
* @return the credentials
*/
@GetCredentialsApiDocs
Expand All @@ -101,8 +102,8 @@ public ResponseEntity<PageImpl<CredentialsResponse>> getCredentials(@Parameter(n
) @RequestParam(required = false, defaultValue = "createdAt") String sortColumn,
@Parameter(name = "sortTpe", description = "Sort order", examples = { @ExampleObject(value = "desc", name = "Descending order"), @ExampleObject(value = "asc", name = "Ascending order") }) @RequestParam(required = false, defaultValue = "desc") String sortTpe,
@AsJwtParam @RequestParam(name = StringPool.AS_JWT, defaultValue = "false") boolean asJwt,
Principal principal) {
log.debug("Received request to get credentials. BPN: {}", getBPNFromToken(principal));
Authentication authentication) {
log.debug("Received request to get credentials. BPN: {}", TokenParsingUtils.getBPNFromToken(authentication));
final GetCredentialsCommand command;
command = GetCredentialsCommand.builder()
.credentialId(credentialId)
Expand All @@ -113,7 +114,7 @@ public ResponseEntity<PageImpl<CredentialsResponse>> getCredentials(@Parameter(n
.pageNumber(pageNumber)
.size(size)
.asJwt(asJwt)
.callerBPN(getBPNFromToken(principal))
.callerBPN(TokenParsingUtils.getBPNFromToken(authentication))
.build();
return ResponseEntity.status(HttpStatus.OK).body(issuersCredentialService.getCredentials(command));
}
Expand All @@ -139,14 +140,14 @@ public ResponseEntity<Map<String, Object>> credentialsValidation(@RequestBody Cr
*
* @param holderDid the holder did
* @param data the data
* @param principal the principal
* @param authentication the authentication
* @return the response entity
*/
@PostMapping(path = RestURI.ISSUERS_CREDENTIALS, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@IssueVerifiableCredentialUsingBaseWalletApiDocs
public ResponseEntity<CredentialsResponse> issueCredentialUsingBaseWallet(@Parameter(description = "Holder DID", examples = {@ExampleObject(description = "did", name = "did", value = "did:web:localhost:BPNL000000000000")}) @RequestParam(name = "holderDid") String holderDid, @RequestBody Map<String, Object> data, Principal principal,
public ResponseEntity<CredentialsResponse> issueCredentialUsingBaseWallet(@Parameter(description = "Holder DID", examples = {@ExampleObject(description = "did", name = "did", value = "did:web:localhost:BPNL000000000000")}) @RequestParam(name = "holderDid") String holderDid, @RequestBody Map<String, Object> data, Authentication authentication,
@AsJwtParam @RequestParam(name = StringPool.AS_JWT, defaultValue = "false") boolean asJwt) {
log.debug("Received request to issue verifiable credential. BPN: {}", getBPNFromToken(principal));
return ResponseEntity.status(HttpStatus.CREATED).body(issuersCredentialService.issueCredentialUsingBaseWallet(holderDid, data, asJwt, getBPNFromToken(principal)));
log.debug("Received request to issue verifiable credential. BPN: {}", TokenParsingUtils.getBPNFromToken(authentication));
return ResponseEntity.status(HttpStatus.CREATED).body(issuersCredentialService.issueCredentialUsingBaseWallet(holderDid, data, asJwt, TokenParsingUtils.getBPNFromToken(authentication)));
}
}
Loading