diff --git a/.config/spotbugs-excludes.xml b/.config/spotbugs-excludes.xml index e38b12df62..ff2379382f 100644 --- a/.config/spotbugs-excludes.xml +++ b/.config/spotbugs-excludes.xml @@ -25,13 +25,13 @@ - + - + diff --git a/charts/irs-helm/templates/configmap-spring-app-config.yaml b/charts/irs-helm/templates/configmap-spring-app-config.yaml index c3961c4b2a..7032da7f92 100644 --- a/charts/irs-helm/templates/configmap-spring-app-config.yaml +++ b/charts/irs-helm/templates/configmap-spring-app-config.yaml @@ -34,6 +34,11 @@ data: irs: apiUrl: {{ tpl (.Values.irsUrl | default "http://localhost") . | quote }} + security: + api: + keys: + admin: {{ tpl (.Values.apiKeyAdmin | default "") . | quote }} + regular: {{ tpl (.Values.apiKeyRegular | default "") . | quote }} blobstore: endpoint: {{ tpl (.Values.minioUrl | default "") . | quote }} @@ -62,9 +67,6 @@ data: token-uri: {{ tpl (.Values.oauth2.clientTokenUri | default "http://localhost") . | quote }} portal: token-uri: {{ tpl (.Values.oauth2.clientTokenUri | default "http://localhost") . | quote }} - resourceserver: - jwt: - jwk-set-uri: {{ tpl (.Values.oauth2.jwkSetUri | default "http://localhost") . | quote }} digitalTwinRegistry: descriptorEndpoint: {{ tpl (.Values.digitalTwinRegistry.descriptorEndpoint | default "") . | quote }} @@ -145,13 +147,6 @@ data: {{- end }} {{- end }} - apiAllowedBpn: {{ tpl (.Values.bpn | default "") . | quote }} - - oauth: - resourceClaim: {{ tpl (.Values.oauth.resourceClaim | default "resource_access") . | quote }} - irsNamespace: {{ tpl (.Values.oauth.irsNamespace | default "") . | quote }} - roles: {{ tpl (.Values.oauth.roles | default "roles") . | quote }} - {{- if .Values.config.content }} {{- tpl (toYaml .Values.config.content) . | nindent 4 }} {{- end }} diff --git a/charts/irs-helm/values.yaml b/charts/irs-helm/values.yaml index 32f565aff3..3e066f347d 100644 --- a/charts/irs-helm/values.yaml +++ b/charts/irs-helm/values.yaml @@ -105,7 +105,8 @@ readinessProbe: # IRS Configuration # ##################### irsUrl: # "https://" -bpn: # BPN for this IRS instance; only users with this BPN are allowed to access the API +apiKeyAdmin: # Api key to access API with admin role +apiKeyRegular: # Api key to access API with regular/view role ingress: enabled: false @@ -153,7 +154,6 @@ oauth2: clientId: # clientSecret: # clientTokenUri: # - jwkSetUri: # portal: oauth2: clientId: # @@ -217,11 +217,6 @@ ess: policydefinitionsPath: /management/v2/policydefinitions # EDC management API "policydefinitions" path - used for notification policy definition creation contractdefinitionsPath: /management/v2/contractdefinitions # EDC management API "contractdefinitions" path - used for notification contract definitions creation -oauth: - resourceClaim: "resource_access" # Name of the JWT claim for roles - irsNamespace: "Cl20-CX-IRS" # Namespace for the IRS roles - roles: "roles" # Name of the list of roles within the IRS namespace - config: # If true, the config provided below will completely replace the configmap. # In this case, you need to provide all required config values defined above yourself! diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/IrsApplication.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/IrsApplication.java index 9d4bbbf566..81c0e0fa27 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/IrsApplication.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/IrsApplication.java @@ -26,6 +26,8 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; import org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration; import org.springframework.boot.context.TypeExcludeFilter; import org.springframework.cache.annotation.EnableCaching; @@ -37,7 +39,7 @@ /** * Application entry point. */ -@SpringBootApplication(exclude = WebSocketServletAutoConfiguration.class) +@SpringBootApplication(exclude = { SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class, WebSocketServletAutoConfiguration.class }) @EnableScheduling @EnableCaching @EnableAsync diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/DependenciesHealthMetricsExportConfiguration.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/DependenciesHealthMetricsExportConfiguration.java index fee6f78ca5..9016948bec 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/DependenciesHealthMetricsExportConfiguration.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/DependenciesHealthMetricsExportConfiguration.java @@ -73,7 +73,7 @@ private void registerOverallHealthMetric(final MeterRegistry registry, log.debug("Registering metric '{}'", metricDescriptor.name); final ToDoubleFunction statusProvider = // - healthIndicator -> HealthStatusHelper.healthStatustoNumeric(overallStatus(healthIndicator)); + healthIndicator -> HealthStatusHelper.healthStatusToNumeric(overallStatus(healthIndicator)); Gauge.builder(metricDescriptor.name, dependenciesHealthIndicator, statusProvider) .description(metricDescriptor.description()) @@ -103,7 +103,7 @@ private void registerIrsDependencyHealthMetric(final MeterRegistry registry, log.debug("Registering metric '{}' tag '{}'", metricDescriptor.name, dependencyName); final ToDoubleFunction statusProvider = // - healthIndicator -> HealthStatusHelper.healthStatustoNumeric( + healthIndicator -> HealthStatusHelper.healthStatusToNumeric( getIrsDependencyStatus(healthIndicator, dependencyName)); Gauge.builder(metricDescriptor.name, dependenciesHealthIndicator, statusProvider) diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/HealthMetricsExportConfiguration.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/HealthMetricsExportConfiguration.java index 4f3f323fcf..d56559f3e1 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/HealthMetricsExportConfiguration.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/HealthMetricsExportConfiguration.java @@ -55,7 +55,7 @@ private void registerIrsHealthMetrics(final MeterRegistry registry, final Health log.debug("Registering metric '{}'", metricName); final ToDoubleFunction statusProvider = // - healthEndpoint -> HealthStatusHelper.healthStatustoNumeric(getIrsStatus(healthEndpoint)); + healthEndpoint -> HealthStatusHelper.healthStatusToNumeric(getIrsStatus(healthEndpoint)); Gauge.builder(metricName, irsHealthEndpoint, statusProvider) .description("The IRS health status.") diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/HealthStatusHelper.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/HealthStatusHelper.java index c531a0e4ca..305bccd738 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/HealthStatusHelper.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/HealthStatusHelper.java @@ -45,7 +45,7 @@ public class HealthStatusHelper { * @param status the health status * @return the numeric representation of the health status */ - public static int healthStatustoNumeric(final Status status) { + public static int healthStatusToNumeric(final Status status) { // see Spring documentation - map health indicators to metrics: // https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/ diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/converter/IrsTokenParser.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/converter/IrsTokenParser.java deleted file mode 100644 index 198e767930..0000000000 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/converter/IrsTokenParser.java +++ /dev/null @@ -1,66 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021,2022,2023 - * 2022: ZF Friedrichshafen AG - * 2022: ISTOS GmbH - * 2022,2023: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * 2022,2023: BOSCH AG - * Copyright (c) 2021,2022,2023 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.irs.configuration.converter; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import com.nimbusds.jose.shaded.gson.internal.LinkedTreeMap; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.jwt.Jwt; - -/** - * Parsing JWT - retrieving resource_access claim with IRS roles. - */ -@Slf4j -@AllArgsConstructor -public class IrsTokenParser { - - private String resourceAccessClaim; - private String irsResourceAccess; - private String roles; - - /** - * Parsing JWT - retrieving resource_access claim with IRS roles. - * - * @param jwt source - * @return set of roles from token - */ - public Set extractIrsRolesFromToken(final Jwt jwt) { - return Optional.ofNullable(jwt.getClaim(resourceAccessClaim)) - .map(LinkedTreeMap.class::cast) - .map(accesses -> accesses.get(irsResourceAccess)) - .map(LinkedTreeMap.class::cast) - .map(irsAccesses -> irsAccesses.get(roles)) - .map(irsRoles -> ((List) irsRoles).stream() - .map(SimpleGrantedAuthority::new) - .collect(Collectors.toSet())) - .orElse(Collections.emptySet()); - } -} diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/converter/JwtAuthenticationConverter.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/converter/JwtAuthenticationConverter.java deleted file mode 100644 index 81f4b71ced..0000000000 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/converter/JwtAuthenticationConverter.java +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021,2022,2023 - * 2022: ZF Friedrichshafen AG - * 2022: ISTOS GmbH - * 2022,2023: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * 2022,2023: BOSCH AG - * Copyright (c) 2021,2022,2023 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.irs.configuration.converter; - -import java.util.Collection; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import lombok.AllArgsConstructor; -import org.jetbrains.annotations.NotNull; -import org.springframework.core.convert.converter.Converter; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; - -/** - * JWT Converter - */ -@AllArgsConstructor -public class JwtAuthenticationConverter implements Converter { - - private JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter; - private IrsTokenParser irsTokenParser; - - @Override - public AbstractAuthenticationToken convert(final @NotNull Jwt source) { - final Collection grantedAuthorities = jwtGrantedAuthoritiesConverter.convert(source); - - final Collection authorities = Stream.concat( - grantedAuthorities != null ? grantedAuthorities.stream() : Stream.empty(), - irsTokenParser.extractIrsRolesFromToken(source).stream()).collect(Collectors.toSet()); - - return new JwtAuthenticationToken(source, authorities); - } -} - diff --git a/irs-common/src/main/java/org/eclipse/tractusx/irs/common/auth/AuthorizationService.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/ApiKeyAuthentication.java similarity index 56% rename from irs-common/src/main/java/org/eclipse/tractusx/irs/common/auth/AuthorizationService.java rename to irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/ApiKeyAuthentication.java index f695499d15..7584d033c8 100644 --- a/irs-common/src/main/java/org/eclipse/tractusx/irs/common/auth/AuthorizationService.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/ApiKeyAuthentication.java @@ -21,33 +21,30 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.irs.common.auth; +package org.eclipse.tractusx.irs.configuration.security; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; +import org.springframework.security.authentication.AbstractAuthenticationToken; /** - * BPN contains in JWT Token matches BPN under which IRS product is registered. + * Api key authentication representation */ -@Service -public class AuthorizationService { +public class ApiKeyAuthentication extends AbstractAuthenticationToken { - private final SecurityHelperService securityHelperService; - private final String apiAllowedBpn; + private final ApiKeyAuthority apiKeyAuthority; - public AuthorizationService(@Value("${apiAllowedBpn:}") final String apiAllowedBpn) { - this.securityHelperService = new SecurityHelperService(); - this.apiAllowedBpn = apiAllowedBpn; + public ApiKeyAuthentication(final ApiKeyAuthority apiKeyAuthority) { + super(apiKeyAuthority.authorities()); + this.apiKeyAuthority = apiKeyAuthority; + setAuthenticated(true); } - public boolean verifyBpn() { - if (StringUtils.isBlank(apiAllowedBpn)) { - return false; - } - - final String bpnFromToken = securityHelperService.getBpnClaim(); - return apiAllowedBpn.equals(bpnFromToken); + @Override + public Object getCredentials() { + return apiKeyAuthority.apiKey(); } + @Override + public Object getPrincipal() { + return apiKeyAuthority.apiKey(); + } } diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/ApiKeyAuthenticationFilter.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/ApiKeyAuthenticationFilter.java new file mode 100644 index 0000000000..484254e393 --- /dev/null +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/ApiKeyAuthenticationFilter.java @@ -0,0 +1,82 @@ +/******************************************************************************** + * Copyright (c) 2021,2022,2023 + * 2022: ZF Friedrichshafen AG + * 2022: ISTOS GmbH + * 2022,2023: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 2022,2023: BOSCH AG + * Copyright (c) 2021,2022,2023 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.irs.configuration.security; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.eclipse.tractusx.irs.dtos.ErrorResponse; +import org.eclipse.tractusx.irs.util.JsonUtil; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +/** + * Filter to execute authentication based on X-API-KEY header + */ +@RequiredArgsConstructor +class ApiKeyAuthenticationFilter extends OncePerRequestFilter { + + private final AuthenticationService authenticationService; + private final JsonUtil jsonUtil; + + @Override + protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, + final FilterChain filterChain) throws ServletException, IOException { + try { + final Authentication authentication = authenticationService.getAuthentication(request); + SecurityContextHolder.getContext().setAuthentication(authentication); + } catch (final BadCredentialsException exception) { + unauthorizedResponse(response, exception); + } + + filterChain.doFilter(request, response); + } + + private void unauthorizedResponse(final HttpServletResponse servletResponse, final Exception exception) throws IOException { + servletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + servletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); + servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.name()); + + final ErrorResponse errorResponse = ErrorResponse.builder() + .withStatusCode(HttpStatus.UNAUTHORIZED) + .withError(exception.getMessage()) + .build(); + + + try (PrintWriter writer = servletResponse.getWriter()) { + writer.print(jsonUtil.asString(errorResponse)); + writer.flush(); + } + } +} diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/ApiKeyAuthority.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/ApiKeyAuthority.java new file mode 100644 index 0000000000..6b61ab6047 --- /dev/null +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/ApiKeyAuthority.java @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2021,2022,2023 + * 2022: ZF Friedrichshafen AG + * 2022: ISTOS GmbH + * 2022,2023: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 2022,2023: BOSCH AG + * Copyright (c) 2021,2022,2023 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.irs.configuration.security; + +import java.util.List; + +import org.springframework.security.core.GrantedAuthority; + +/** + * @param apiKey + * @param authorities + */ +public record ApiKeyAuthority(String apiKey, List authorities) { + + @SuppressWarnings("PMD.ShortMethodName") + public static ApiKeyAuthority of(final String apiKey, final List authorities) { + return new ApiKeyAuthority(apiKey, authorities); + } + +} diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/ApiKeysConfiguration.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/ApiKeysConfiguration.java new file mode 100644 index 0000000000..c8be770750 --- /dev/null +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/ApiKeysConfiguration.java @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2021,2022,2023 + * 2022: ZF Friedrichshafen AG + * 2022: ISTOS GmbH + * 2022,2023: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 2022,2023: BOSCH AG + * Copyright (c) 2021,2022,2023 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.irs.configuration.security; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Setter; +import org.eclipse.tractusx.irs.common.auth.IrsRoles; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +/** + * Configuration class for api keys + */ +@Component +@ConfigurationProperties(prefix = "irs.security.api.keys") +@Validated +@Setter +class ApiKeysConfiguration { + + private static final int MIN_API_KEY_SIZE = 20; + + @NotBlank + @Size(min = MIN_API_KEY_SIZE) + private String admin; + + @NotBlank + @Size(min = MIN_API_KEY_SIZE) + private String regular; + + /* package */ ApiKeyAuthority authorityOf(final String apiKey) { + if (apiKey.equals(admin)) { + return ApiKeyAuthority.of(apiKey, AuthorityUtils.createAuthorityList(IrsRoles.ADMIN_IRS)); + } else if (apiKey.equals(regular)) { + return ApiKeyAuthority.of(regular, AuthorityUtils.createAuthorityList(IrsRoles.VIEW_IRS)); + } + + throw new BadCredentialsException("Wrong ApiKey"); + } +} diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/AuthenticationService.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/AuthenticationService.java new file mode 100644 index 0000000000..aafebcfc39 --- /dev/null +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/AuthenticationService.java @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (c) 2021,2022,2023 + * 2022: ZF Friedrichshafen AG + * 2022: ISTOS GmbH + * 2022,2023: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 2022,2023: BOSCH AG + * Copyright (c) 2021,2022,2023 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.irs.configuration.security; + +import com.apicatalog.jsonld.StringUtils; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Service; + +/** + * Check X-API-KEY header against api keys from config + */ +@RequiredArgsConstructor +@Service +public class AuthenticationService { + + private static final String AUTH_TOKEN_HEADER_NAME = "X-API-KEY"; + + private final ApiKeysConfiguration apiKeysConfiguration; + + public Authentication getAuthentication(final HttpServletRequest request) { + final String apiKeyHeader = request.getHeader(AUTH_TOKEN_HEADER_NAME); + if (StringUtils.isBlank(apiKeyHeader)) { + throw new BadCredentialsException("Wrong ApiKey"); + } + + return new ApiKeyAuthentication(apiKeysConfiguration.authorityOf(apiKeyHeader)); + } +} diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/SecurityConfiguration.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/SecurityConfiguration.java similarity index 58% rename from irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/SecurityConfiguration.java rename to irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/SecurityConfiguration.java index d7a46ab4bd..432fce53fa 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/SecurityConfiguration.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/configuration/security/SecurityConfiguration.java @@ -21,17 +21,23 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -package org.eclipse.tractusx.irs.configuration; +package org.eclipse.tractusx.irs.configuration.security; +import static java.util.Arrays.stream; import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; +import java.io.IOException; import java.time.Duration; import java.util.List; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import org.eclipse.tractusx.irs.common.ApiConstants; -import org.eclipse.tractusx.irs.configuration.converter.IrsTokenParser; -import org.eclipse.tractusx.irs.configuration.converter.JwtAuthenticationConverter; -import org.springframework.beans.factory.annotation.Value; +import org.eclipse.tractusx.irs.util.JsonUtil; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; @@ -40,16 +46,18 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; -import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter; import org.springframework.security.web.header.writers.PermissionsPolicyHeaderWriter; import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; import org.springframework.security.web.util.matcher.AnyRequestMatcher; +import org.springframework.util.AntPathMatcher; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.OncePerRequestFilter; /** * Security config bean @@ -57,21 +65,22 @@ @Configuration @EnableWebSecurity @EnableMethodSecurity +@SuppressWarnings({ "PMD.ExcessiveImports" }) public class SecurityConfiguration { - private static final String[] WHITELIST = { - "/actuator/health", - "/actuator/health/readiness", - "/actuator/health/liveness", - "/actuator/prometheus", - "/api/swagger-ui/**", - "/api/api-docs", - "/api/api-docs.yaml", - "/api/api-docs/swagger-config", - "/" + ApiConstants.API_PREFIX_INTERNAL + "/endpoint-data-reference", - "/ess/mock/notification/receive", - "/ess/notification/receive", - "/ess/notification/receive-recursive" + private static final String[] WHITELIST = { "/actuator/health", + "/actuator/health/readiness", + "/actuator/health/liveness", + "/actuator/prometheus", + "/api/swagger-ui/**", + "/api/api-docs", + "/api/api-docs.yaml", + "/api/api-docs/swagger-config", + "/favicon.ico", + "/" + ApiConstants.API_PREFIX_INTERNAL + "/endpoint-data-reference", + "/ess/mock/notification/receive", + "/ess/notification/receive", + "/ess/notification/receive-recursive" }; private static final long HSTS_MAX_AGE_DAYS = 365; private static final String ONLY_SELF_SCRIPT_SRC = "script-src 'self'"; @@ -80,7 +89,7 @@ public class SecurityConfiguration { @SuppressWarnings("PMD.SignatureDeclareThrowsException") @Bean /* package */ SecurityFilterChain securityFilterChain(final HttpSecurity httpSecurity, - final JwtAuthenticationConverter jwtAuthenticationConverter) throws Exception { + final AuthenticationService authenticationService) throws Exception { httpSecurity.httpBasic(AbstractHttpConfigurer::disable); httpSecurity.formLogin(AbstractHttpConfigurer::disable); httpSecurity.csrf(AbstractHttpConfigurer::disable); @@ -88,39 +97,53 @@ public class SecurityConfiguration { httpSecurity.cors(Customizer.withDefaults()); httpSecurity.headers(headers -> headers.httpStrictTransportSecurity( - httpStrictTransportSecurity -> - httpStrictTransportSecurity.maxAgeInSeconds(Duration.ofDays(HSTS_MAX_AGE_DAYS).toSeconds()) - .includeSubDomains(true) - .preload(true) - .requestMatcher(AnyRequestMatcher.INSTANCE))); + httpStrictTransportSecurity -> httpStrictTransportSecurity.maxAgeInSeconds( + Duration.ofDays(HSTS_MAX_AGE_DAYS).toSeconds()).includeSubDomains(true).preload(true).requestMatcher(AnyRequestMatcher.INSTANCE))); - httpSecurity.headers(headers -> headers.xssProtection(xXssConfig -> - xXssConfig.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))); + httpSecurity.headers(headers -> headers.xssProtection(xXssConfig -> xXssConfig.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))); httpSecurity.headers(headers -> headers.addHeaderWriter(new ContentSecurityPolicyHeaderWriter(ONLY_SELF_SCRIPT_SRC))); httpSecurity.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)); - httpSecurity.headers(headers -> headers.addHeaderWriter(new ReferrerPolicyHeaderWriter( - ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN))); + httpSecurity.headers(headers -> headers.addHeaderWriter(new ReferrerPolicyHeaderWriter(ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN))); httpSecurity.headers(headers -> headers.addHeaderWriter(new PermissionsPolicyHeaderWriter(PERMISSION_POLICY))); - httpSecurity.sessionManagement(sessionManagement -> sessionManagement - .sessionCreationPolicy(STATELESS)); + httpSecurity.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(STATELESS)); - httpSecurity.authorizeHttpRequests(auth -> auth - .requestMatchers(WHITELIST) - .permitAll() - .requestMatchers("/**") - .authenticated()); + httpSecurity.authorizeHttpRequests(auth -> auth.requestMatchers(WHITELIST).permitAll().requestMatchers("/**").authenticated()); - httpSecurity.oauth2ResourceServer(oauth2ResourceServer -> - oauth2ResourceServer.jwt(jwt -> - jwt.jwtAuthenticationConverter(jwtAuthenticationConverter))) - .oauth2Client(Customizer.withDefaults()); + httpSecurity.addFilterBefore(new IgnoreWhitelistedPathFilter(new ApiKeyAuthenticationFilter(authenticationService, new JsonUtil())), UsernamePasswordAuthenticationFilter.class); + + httpSecurity.exceptionHandling(AbstractHttpConfigurer::disable); return httpSecurity.build(); } + /** + * Dont execute delegate filter on whitelisted paths + */ + @RequiredArgsConstructor + private static final class IgnoreWhitelistedPathFilter extends OncePerRequestFilter { + + private final Filter delegate; + private final AntPathMatcher pathMatcher = new AntPathMatcher(); + + @Override + protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws + ServletException, IOException { + if (isNotWhitelisted(request)) { + delegate.doFilter(request, response, filterChain); + } else { + filterChain.doFilter(request, response); + } + } + + private boolean isNotWhitelisted(final HttpServletRequest request) { + return stream(WHITELIST).noneMatch(path -> pathMatcher.match(path, request.getRequestURI())); + } + } + + @Bean /* package */ CorsConfigurationSource corsConfigurationSource() { final CorsConfiguration configuration = new CorsConfiguration(); @@ -134,16 +157,4 @@ public class SecurityConfiguration { source.registerCorsConfiguration("/**", configuration); return source; } - - @Bean - /* package */ IrsTokenParser irsTokenParser(@Value("${oauth.resourceClaim}") final String resourceAccessClaim, - @Value("${oauth.irsNamespace}") final String irsResourceAccess, - @Value("${oauth.roles}") final String roles) { - return new IrsTokenParser(resourceAccessClaim, irsResourceAccess, roles); - } - - @Bean - /* package */ JwtAuthenticationConverter jwtAuthenticationConverter(final IrsTokenParser irsTokenParser) { - return new JwtAuthenticationConverter(new JwtGrantedAuthoritiesConverter(), irsTokenParser); - } } diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/controllers/BatchController.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/controllers/BatchController.java index f638ac0bcb..d8c7f4db7d 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/controllers/BatchController.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/controllers/BatchController.java @@ -49,7 +49,6 @@ import org.eclipse.tractusx.irs.component.RegisterBatchOrder; import org.eclipse.tractusx.irs.component.RegisterBpnInvestigationBatchOrder; import org.eclipse.tractusx.irs.dtos.ErrorResponse; -import org.eclipse.tractusx.irs.common.auth.AuthorizationService; import org.eclipse.tractusx.irs.services.CreationBatchService; import org.eclipse.tractusx.irs.services.QueryBatchService; import org.eclipse.tractusx.irs.services.timeouts.CancelBatchProcessingService; @@ -79,7 +78,6 @@ public class BatchController { private final CreationBatchService creationBatchService; private final QueryBatchService queryBatchService; private final CancelBatchProcessingService cancelBatchProcessingService; - private final AuthorizationService authorizationService; @Operation(operationId = "registerOrder", summary = "Registers an IRS order with an array of {globalAssetIds}. " @@ -116,7 +114,7 @@ public class BatchController { }) @PostMapping("/orders") @ResponseStatus(HttpStatus.CREATED) - @PreAuthorize("@authorizationService.verifyBpn() && hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") + @PreAuthorize("hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") public BatchOrderCreated registerBatchOrder(final @Valid @RequestBody RegisterBatchOrder request) { final UUID batchOrderId = creationBatchService.create(request); return BatchOrderCreated.builder().id(batchOrderId).build(); @@ -155,7 +153,7 @@ public BatchOrderCreated registerBatchOrder(final @Valid @RequestBody RegisterBa }) @PostMapping("/ess/orders") @ResponseStatus(HttpStatus.CREATED) - @PreAuthorize("@authorizationService.verifyBpn() && hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") + @PreAuthorize("hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") public BatchOrderCreated registerESSInvestigationOrder(final @Valid @RequestBody RegisterBpnInvestigationBatchOrder request) { final UUID batchOrderId = creationBatchService.create(request); return BatchOrderCreated.builder().id(batchOrderId).build(); @@ -199,7 +197,7 @@ public BatchOrderCreated registerESSInvestigationOrder(final @Valid @RequestBody }), }) @GetMapping("/orders/{orderId}") - @PreAuthorize("@authorizationService.verifyBpn() && hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") + @PreAuthorize("hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") public BatchOrderResponse getBatchOrder( @Parameter(description = "Id of the order.", schema = @Schema(implementation = UUID.class), name = "orderId", example = "6c311d29-5753-46d4-b32c-19b918ea93b0") @Size(min = IrsAppConstants.JOB_ID_SIZE, @@ -245,7 +243,7 @@ public BatchOrderResponse getBatchOrder( }), }) @GetMapping("/orders/{orderId}/batches/{batchId}") - @PreAuthorize("@authorizationService.verifyBpn() && hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") + @PreAuthorize("hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") public BatchResponse getBatch( @Parameter(description = "Id of the order.", schema = @Schema(implementation = UUID.class), name = "orderId", example = "6c311d29-5753-46d4-b32c-19b918ea93b0") @Size(min = IrsAppConstants.JOB_ID_SIZE, @@ -294,7 +292,7 @@ public BatchResponse getBatch( }), }) @PutMapping("/orders/{orderId}") - @PreAuthorize("@authorizationService.verifyBpn() && hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") + @PreAuthorize("hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") public BatchOrderResponse cancelBatchOrder( @Parameter(description = "Id of the order.", schema = @Schema(implementation = UUID.class), name = "orderId", example = "6c311d29-5753-46d4-b32c-19b918ea93b0") @Size(min = IrsAppConstants.JOB_ID_SIZE, diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/controllers/IrsController.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/controllers/IrsController.java index a3fb4bea4c..2324ee45e5 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/controllers/IrsController.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/controllers/IrsController.java @@ -57,7 +57,6 @@ import org.eclipse.tractusx.irs.connector.job.IrsTimer; import org.eclipse.tractusx.irs.dtos.ErrorResponse; import org.eclipse.tractusx.irs.semanticshub.AspectModels; -import org.eclipse.tractusx.irs.common.auth.AuthorizationService; import org.eclipse.tractusx.irs.services.IrsItemGraphQueryService; import org.eclipse.tractusx.irs.services.SemanticHubService; import org.eclipse.tractusx.irs.services.validation.SchemaNotFoundException; @@ -92,7 +91,6 @@ public class IrsController { private final IrsItemGraphQueryService itemJobService; private final SemanticHubService semanticHubService; - private final AuthorizationService authorizationService; @Operation(operationId = "registerJobForGlobalAssetId", summary = "Register an IRS job to retrieve an item graph for given {globalAssetId}.", @@ -128,7 +126,7 @@ public class IrsController { @IrsTimer("registerjob") @PostMapping("/jobs") @ResponseStatus(HttpStatus.CREATED) - @PreAuthorize("@authorizationService.verifyBpn() && hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") + @PreAuthorize("hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") public JobHandle registerJobForGlobalAssetId(final @Valid @RequestBody RegisterJob request) { return itemJobService.registerItemJob(request); } @@ -179,7 +177,7 @@ public JobHandle registerJobForGlobalAssetId(final @Valid @RequestBody RegisterJ }) @IrsTimer("getjob") @GetMapping("/jobs/{id}") - @PreAuthorize("@authorizationService.verifyBpn() && hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") + @PreAuthorize("hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") public ResponseEntity getJobById( @Parameter(description = "Id of the job.", schema = @Schema(implementation = UUID.class), name = "id", example = "6c311d29-5753-46d4-b32c-19b918ea93b0") @Size(min = IrsAppConstants.JOB_ID_SIZE, @@ -232,7 +230,7 @@ public ResponseEntity getJobById( }) @IrsTimer("canceljob") @PutMapping("/jobs/{id}") - @PreAuthorize("@authorizationService.verifyBpn() && hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") + @PreAuthorize("hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") public Job cancelJobByJobId( @Parameter(description = "Id of the job.", schema = @Schema(implementation = UUID.class), name = "id", example = "6c311d29-5753-46d4-b32c-19b918ea93b0") @Size(min = IrsAppConstants.JOB_ID_SIZE, @@ -274,7 +272,7 @@ public Job cancelJobByJobId( @IrsTimer("getjobbystate") @GetMapping("/jobs") @PageableAsQueryParam - @PreAuthorize("@authorizationService.verifyBpn() && hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") + @PreAuthorize("hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") public PageResult getJobsByState( @Valid @ParameterObject @Parameter(description = "Requested job states.", in = QUERY, explode = Explode.FALSE, array = @ArraySchema(schema = @Schema(implementation = JobState.class), maxItems = Integer.MAX_VALUE)) @@ -308,7 +306,7 @@ public PageResult getJobsByState( }), }) @GetMapping("/aspectmodels") - @PreAuthorize("@authorizationService.verifyBpn() && hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") + @PreAuthorize("hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") public AspectModels getAllAvailableAspectModels() throws SchemaNotFoundException { return semanticHubService.getAllAspectModels(); } diff --git a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/controller/EssController.java b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/controller/EssController.java index 2ba3374983..04bb98e83e 100644 --- a/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/controller/EssController.java +++ b/irs-api/src/main/java/org/eclipse/tractusx/irs/ess/controller/EssController.java @@ -41,7 +41,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.tractusx.irs.ess.service.EssService; -import org.eclipse.tractusx.irs.common.auth.AuthorizationService; import org.eclipse.tractusx.irs.common.auth.IrsRoles; import org.eclipse.tractusx.irs.component.JobHandle; import org.eclipse.tractusx.irs.component.Jobs; @@ -73,7 +72,6 @@ class EssController { private final EssService essService; - private final AuthorizationService authorizationService; @Operation(operationId = "registerBPNInvestigation", summary = "Registers an IRS job to start an investigation if a given bpn is contained in a part chain of a given globalAssetId.", @@ -108,7 +106,7 @@ class EssController { }) @PostMapping("/bpn/investigations") @ResponseStatus(HttpStatus.CREATED) - @PreAuthorize("@authorizationService.verifyBpn() && hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") + @PreAuthorize("hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") public JobHandle registerBPNInvestigation(final @Valid @RequestBody RegisterBpnInvestigationJob request) { return essService.startIrsJob(request); } @@ -151,7 +149,7 @@ public JobHandle registerBPNInvestigation(final @Valid @RequestBody RegisterBpnI }), }) @GetMapping("/bpn/investigations/{id}") - @PreAuthorize("@authorizationService.verifyBpn() && hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") + @PreAuthorize("hasAnyAuthority('" + IrsRoles.ADMIN_IRS + "', '" + IrsRoles.VIEW_IRS + "')") public Jobs getBPNInvestigation( @Parameter(description = "Id of the job.", schema = @Schema(implementation = UUID.class), name = "id", example = "6c311d29-5753-46d4-b32c-19b918ea93b0") @Valid @PathVariable final UUID id) { diff --git a/irs-api/src/main/resources/application.yml b/irs-api/src/main/resources/application.yml index 52f8cd87b1..4132ba837f 100644 --- a/irs-api/src/main/resources/application.yml +++ b/irs-api/src/main/resources/application.yml @@ -22,9 +22,7 @@ spring: token-uri: ${OAUTH2_CLIENT_TOKEN_URI:https://default} # OAuth2 endpoint to request tokens using the client credentials portal: token-uri: ${PORTAL_OAUTH2_CLIENT_TOKEN_URI:https://default} # OAuth2 endpoint to request tokens using the client credentials - resourceserver: - jwt: - jwk-set-uri: ${OAUTH2_JWK_SET_URI:https://default} # OAuth2 endpoint to request the JWK set + management: # Spring management API config, see https://spring.io/guides/gs/centralized-configuration/ endpoints: @@ -96,6 +94,11 @@ irs: # Application config completed: P7D # ISO 8601 Duration cron: expression: "*/10 * * * * ?" # Determines how often the number of stored jobs is updated in the metrics API. + security: + api: + keys: + admin: ${API_KEY_ADMIN} + regular: ${API_KEY_REGULAR} blobstore: endpoint: "${MINIO_URL}" # S3 compatible API endpoint (e.g. Minio) @@ -238,10 +241,3 @@ ess: mockEdcResult: { } # Mocked BPN Investigation results mockRecursiveEdcAsset: # Mocked BPN Recursive Investigation results -apiAllowedBpn: ${API_ALLOWED_BPN:BPNL00000001CRHK} # BPN value that is allowed to access IRS API - -# OAuth2 JWT token parse config. This configures the structure IRS expects when parsing the IRS role of an access token. -oauth: - resourceClaim: "resource_access" # Name of the JWT claim for roles - irsNamespace: "Cl20-CX-IRS" # Namespace for the IRS roles - roles: "roles" # Name of the list of roles within the IRS namespace diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/ControllerTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/ControllerTest.java new file mode 100644 index 0000000000..ae20b160c0 --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/ControllerTest.java @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (c) 2021,2022,2023 + * 2022: ZF Friedrichshafen AG + * 2022: ISTOS GmbH + * 2022,2023: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 2022,2023: BOSCH AG + * Copyright (c) 2021,2022,2023 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.irs; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import jakarta.servlet.http.HttpServletRequest; +import org.eclipse.tractusx.irs.configuration.security.ApiKeyAuthentication; +import org.eclipse.tractusx.irs.configuration.security.ApiKeyAuthority; +import org.eclipse.tractusx.irs.configuration.security.AuthenticationService; +import org.eclipse.tractusx.irs.util.JsonUtil; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.core.authority.AuthorityUtils; + +public abstract class ControllerTest { + + @MockBean + protected AuthenticationService authenticationService; + + protected void authenticateWith(String... roles) { + when(authenticationService.getAuthentication(any(HttpServletRequest.class))) + .thenReturn(new ApiKeyAuthentication( + ApiKeyAuthority.of("123", AuthorityUtils.createAuthorityList(roles)))); + } + +} diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/configuration/HealthStatusHelperTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/configuration/HealthStatusHelperTest.java index 2aab79b058..531a42e8f6 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/configuration/HealthStatusHelperTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/configuration/HealthStatusHelperTest.java @@ -43,6 +43,6 @@ public static List healthStatusToNumeric() { @ParameterizedTest @MethodSource void healthStatusToNumeric(final Status status, final int numericStatus) { - assertThat(HealthStatusHelper.healthStatustoNumeric(status)).isEqualTo(numericStatus); + assertThat(HealthStatusHelper.healthStatusToNumeric(status)).isEqualTo(numericStatus); } } diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/configuration/converter/JwtAuthenticationConverterTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/configuration/converter/JwtAuthenticationConverterTest.java deleted file mode 100644 index b79822fce1..0000000000 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/configuration/converter/JwtAuthenticationConverterTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021,2022,2023 - * 2022: ZF Friedrichshafen AG - * 2022: ISTOS GmbH - * 2022,2023: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * 2022,2023: BOSCH AG - * Copyright (c) 2021,2022,2023 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.irs.configuration.converter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.oauth2.jwt.JwtClaimNames.SUB; - -import java.time.Instant; -import java.util.List; -import java.util.Map; - -import com.nimbusds.jose.shaded.gson.internal.LinkedTreeMap; -import org.eclipse.tractusx.irs.common.auth.IrsRoles; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; - -class JwtAuthenticationConverterTest { - - private JwtAuthenticationConverter jwtAuthenticationConverter; - - @BeforeEach - void setUp() { - final String resourceAccessClaim = "resource_access"; - final String irsResourceAccess = "Cl20-CX-IRS"; - final String roles = "roles"; - jwtAuthenticationConverter = new JwtAuthenticationConverter(new JwtGrantedAuthoritiesConverter(), - new IrsTokenParser(resourceAccessClaim, irsResourceAccess, roles)); - } - - @Test - void shouldParseJwtTokenAndFindViewIrsRole() { - // given - final Map irsResourceAccess = new LinkedTreeMap<>(); - final Map irsRoles = new LinkedTreeMap<>(); - irsRoles.put("roles", List.of("view_irs")); - irsResourceAccess.put("Cl20-CX-IRS", irsRoles); - final Jwt jwt = jwt(irsResourceAccess); - - // when - final AbstractAuthenticationToken authenticationToken = jwtAuthenticationConverter.convert(jwt); - - // then - assertThat(authenticationToken).isNotNull(); - assertThat(authenticationToken.getAuthorities()).isNotNull(); - assertThat(authenticationToken.getAuthorities()).contains(new SimpleGrantedAuthority(IrsRoles.VIEW_IRS)); - } - - @Test - void shouldParseJwtTokenAndNotFindIrsRolesWhenWrongKey() { - // given - final Map irsResourceAccess = new LinkedTreeMap<>(); - final Map irsRoles = new LinkedTreeMap<>(); - irsRoles.put("roles", List.of()); - irsResourceAccess.put("Cl20-CX-IRS-WRONG-KEY", irsRoles); - final Jwt jwt = jwt(irsResourceAccess); - - // when - final AbstractAuthenticationToken authenticationToken = jwtAuthenticationConverter.convert(jwt); - - // then - assertThat(authenticationToken).isNotNull(); - assertThat(authenticationToken.getAuthorities()).isNotNull(); - assertThat(authenticationToken.getAuthorities()).isEmpty(); - } - - @Test - void shouldParseJwtTokenAndNotFindIrsRolesWhenWrongRolesKey() { - // given - final Map irsResourceAccess = new LinkedTreeMap<>(); - final Map irsRoles = new LinkedTreeMap<>(); - irsRoles.put("rolesWrong", List.of("view_irs")); - irsResourceAccess.put("Cl20-CX-IRS-WRONG-KEY", irsRoles); - final Jwt jwt = jwt(irsResourceAccess); - - // when - final AbstractAuthenticationToken authenticationToken = jwtAuthenticationConverter.convert(jwt); - - // then - assertThat(authenticationToken).isNotNull(); - assertThat(authenticationToken.getAuthorities()).isNotNull(); - assertThat(authenticationToken.getAuthorities()).isEmpty(); - } - - Jwt jwt(final Map irsResourceAccess) { - final Map claims = new LinkedTreeMap<>(); - claims.putAll(Map.of("resource_access", irsResourceAccess, SUB, "sub", "clientId", "clientId")); - - return new Jwt("token", Instant.now(), Instant.now().plusSeconds(30), Map.of("alg", "none"), claims); - } -} \ No newline at end of file diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/configuration/security/ApiKeyAuthenticationFilterTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/configuration/security/ApiKeyAuthenticationFilterTest.java new file mode 100644 index 0000000000..57ee7e5223 --- /dev/null +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/configuration/security/ApiKeyAuthenticationFilterTest.java @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2021,2022,2023 + * 2022: ZF Friedrichshafen AG + * 2022: ISTOS GmbH + * 2022,2023: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) + * 2022,2023: BOSCH AG + * Copyright (c) 2021,2022,2023 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.irs.configuration.security; + +import static org.junit.jupiter.api.Assertions.*; + +class ApiKeyAuthenticationFilterTest { + +} \ No newline at end of file diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/BatchControllerTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/BatchControllerTest.java index 2e1b5cef34..5f4af60059 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/BatchControllerTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/BatchControllerTest.java @@ -23,6 +23,8 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.controllers; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.eclipse.tractusx.irs.util.TestMother.registerBatchOrder; import static org.eclipse.tractusx.irs.util.TestMother.registerBpnInvestigationBatchOrder; import static org.hamcrest.Matchers.containsString; @@ -36,12 +38,12 @@ import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.tractusx.irs.ControllerTest; import org.eclipse.tractusx.irs.common.auth.IrsRoles; import org.eclipse.tractusx.irs.component.BatchOrderResponse; import org.eclipse.tractusx.irs.component.BatchResponse; import org.eclipse.tractusx.irs.component.RegisterBatchOrder; -import org.eclipse.tractusx.irs.configuration.SecurityConfiguration; -import org.eclipse.tractusx.irs.common.auth.AuthorizationService; +import org.eclipse.tractusx.irs.configuration.security.SecurityConfiguration; import org.eclipse.tractusx.irs.services.CreationBatchService; import org.eclipse.tractusx.irs.services.QueryBatchService; import org.eclipse.tractusx.irs.services.timeouts.CancelBatchProcessingService; @@ -51,12 +53,12 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; -import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.access.AccessDeniedException; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(BatchController.class) @Import(SecurityConfiguration.class) -class BatchControllerTest { +class BatchControllerTest extends ControllerTest { @Autowired private MockMvc mockMvc; @@ -70,21 +72,17 @@ class BatchControllerTest { @MockBean private CancelBatchProcessingService cancelBatchProcessingService; - @MockBean(name = "authorizationService") - private AuthorizationService authorizationService; - @Test - void shouldReturnUnauthorizedWhenAuthenticationIsMissing() throws Exception { - this.mockMvc.perform(post("/irs/orders").contentType(MediaType.APPLICATION_JSON) - .content(new ObjectMapper().writeValueAsString( - registerBatchOrder("urn:uuid:4132cd2b-cbe7-4881-a6b4-39fdc31cca2b")))) - .andExpect(status().isUnauthorized()); + void shouldReturnUnauthorizedWhenAuthenticationIsMissing() { + assertThatThrownBy(() -> this.mockMvc.perform(post("/irs/orders").contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().writeValueAsString( + registerBatchOrder("urn:uuid:4132cd2b-cbe7-4881-a6b4-39fdc31cca2b")))) + ).isInstanceOf(AccessDeniedException.class); } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldReturnBadRequestWhenGlobalAssetIdWithWrongFormat() throws Exception { - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); + authenticateWith(IrsRoles.VIEW_IRS); this.mockMvc.perform(post("/irs/orders").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( @@ -93,9 +91,8 @@ void shouldReturnBadRequestWhenGlobalAssetIdWithWrongFormat() throws Exception { } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldReturnBadRequestWhenBatchSizeNotMod10Compliant() throws Exception { - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); + authenticateWith(IrsRoles.VIEW_IRS); final RegisterBatchOrder registerBatchOrder = registerBatchOrder("MALFORMED_GLOBAL_ASSET"); registerBatchOrder.setBatchSize(33); @@ -105,9 +102,8 @@ void shouldReturnBadRequestWhenBatchSizeNotMod10Compliant() throws Exception { } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldRegisterRegularJobBatchOrder() throws Exception { - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); + authenticateWith(IrsRoles.VIEW_IRS); this.mockMvc.perform(post("/irs/orders").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( @@ -116,9 +112,8 @@ void shouldRegisterRegularJobBatchOrder() throws Exception { } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldRegisterEssJobBatchOrder() throws Exception { - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); + authenticateWith(IrsRoles.VIEW_IRS); this.mockMvc.perform(post("/irs/ess/orders").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( @@ -127,9 +122,8 @@ void shouldRegisterEssJobBatchOrder() throws Exception { } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldReturnBatchOrder() throws Exception { - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); + authenticateWith(IrsRoles.VIEW_IRS); final UUID orderId = UUID.randomUUID(); when(queryBatchService.findOrderById(orderId)).thenReturn(BatchOrderResponse.builder().orderId(orderId).build()); @@ -140,11 +134,11 @@ void shouldReturnBatchOrder() throws Exception { } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldReturnBatch() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + final UUID orderId = UUID.randomUUID(); final UUID batchId = UUID.randomUUID(); - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); when(queryBatchService.findBatchById(orderId, batchId)).thenReturn( BatchResponse.builder().batchId(batchId).orderId(orderId).build()); @@ -155,9 +149,8 @@ void shouldReturnBatch() throws Exception { } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldCancelBatchOrder() throws Exception { - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); + authenticateWith(IrsRoles.VIEW_IRS); final UUID orderId = UUID.randomUUID(); when(queryBatchService.findOrderById(orderId)).thenReturn(BatchOrderResponse.builder().orderId(orderId).build()); diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/IrsControllerTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/IrsControllerTest.java index d266e5ad84..d175fdf001 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/IrsControllerTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/IrsControllerTest.java @@ -24,6 +24,8 @@ package org.eclipse.tractusx.irs.controllers; import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.eclipse.tractusx.irs.util.TestMother.registerBatchOrder; import static org.eclipse.tractusx.irs.util.TestMother.registerJob; import static org.eclipse.tractusx.irs.util.TestMother.registerJobWithDepthAndAspect; import static org.eclipse.tractusx.irs.util.TestMother.registerJobWithUrl; @@ -49,6 +51,9 @@ import java.util.stream.Stream; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import org.eclipse.tractusx.irs.ControllerTest; +import org.eclipse.tractusx.irs.common.auth.IrsRoles; import org.eclipse.tractusx.irs.component.Job; import org.eclipse.tractusx.irs.component.JobHandle; import org.eclipse.tractusx.irs.component.JobStatusResult; @@ -57,10 +62,9 @@ import org.eclipse.tractusx.irs.component.RegisterJob; import org.eclipse.tractusx.irs.component.enums.Direction; import org.eclipse.tractusx.irs.component.enums.JobState; -import org.eclipse.tractusx.irs.configuration.SecurityConfiguration; +import org.eclipse.tractusx.irs.configuration.security.SecurityConfiguration; import org.eclipse.tractusx.irs.semanticshub.AspectModel; import org.eclipse.tractusx.irs.semanticshub.AspectModels; -import org.eclipse.tractusx.irs.common.auth.AuthorizationService; import org.eclipse.tractusx.irs.services.IrsItemGraphQueryService; import org.eclipse.tractusx.irs.services.SemanticHubService; import org.junit.jupiter.api.Test; @@ -73,13 +77,14 @@ import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.server.ResponseStatusException; @WebMvcTest(IrsController.class) @Import(SecurityConfiguration.class) -class IrsControllerTest { +class IrsControllerTest extends ControllerTest { private final UUID jobId = UUID.randomUUID(); @@ -92,8 +97,6 @@ class IrsControllerTest { private IrsItemGraphQueryService service; @MockBean private SemanticHubService semanticHubService; - @MockBean(name = "authorizationService") - private AuthorizationService authorizationService; private static Stream corruptedJobs() { return Stream.of(registerJobWithDepthAndAspect(110, null), @@ -103,11 +106,12 @@ private static Stream corruptedJobs() { } @Test - @WithMockUser(authorities = "view_irs") void initiateJobForGlobalAssetId() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + final UUID returnedJob = UUID.randomUUID(); when(service.registerItemJob(any())).thenReturn(JobHandle.builder().id(returnedJob).build()); - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); + this.mockMvc.perform(post("/irs/jobs").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( @@ -117,26 +121,19 @@ void initiateJobForGlobalAssetId() throws Exception { } @Test - void shouldReturnUnauthorizedStatusWhenAuthenticationIsMissing() throws Exception { - this.mockMvc.perform(post("/irs/jobs").contentType(MediaType.APPLICATION_JSON) - .content(new ObjectMapper().writeValueAsString( - registerJobWithoutDepthAndAspect()))) - .andExpect(status().isUnauthorized()); + void shouldReturnUnauthorizedStatusWhenAuthenticationIsMissing() { + when(authenticationService.getAuthentication(any(HttpServletRequest.class))) + .thenThrow(new BadCredentialsException("Wrong ApiKey")); + + assertThatThrownBy(() -> this.mockMvc.perform(post("/irs/jobs").contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().writeValueAsString( + registerJobWithoutDepthAndAspect()))) + ).isInstanceOf(AccessDeniedException.class); } @Test - @WithMockUser(authorities = "view_irs_wrong_authority") void shouldReturnForbiddenStatusWhenRequiredAuthorityIsMissing() throws Exception { - this.mockMvc.perform(post("/irs/jobs").contentType(MediaType.APPLICATION_JSON) - .content(new ObjectMapper().writeValueAsString( - registerJobWithoutDepthAndAspect()))) - .andExpect(status().isForbidden()); - } - - @Test - @WithMockUser(authorities = "view_irs") - void shouldReturnForbiddenStatusWhenWrongBpnInJwtToken() throws Exception { - when(authorizationService.verifyBpn()).thenReturn(Boolean.FALSE); + authenticateWith("view_irs_wrong_authority"); this.mockMvc.perform(post("/irs/jobs").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( @@ -146,16 +143,18 @@ void shouldReturnForbiddenStatusWhenWrongBpnInJwtToken() throws Exception { @ParameterizedTest @MethodSource("corruptedJobs") - @WithMockUser(authorities = "view_irs") void shouldReturnBadRequestWhenRegisterJobBodyNotValid(final RegisterJob registerJob) throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + this.mockMvc.perform(post("/irs/jobs").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString(registerJob))) .andExpect(status().isBadRequest()); } @Test - @WithMockUser(authorities = "view_irs") void shouldReturnBadRequestWhenRegisterJobHasWrongCallbackUrl() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + this.mockMvc.perform(post("/irs/jobs").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( registerJobWithUrl("hhh://example.com")))) @@ -163,11 +162,11 @@ void shouldReturnBadRequestWhenRegisterJobHasWrongCallbackUrl() throws Exception } @Test - @WithMockUser(authorities = "view_irs") void shouldAcceptCorrectCallbackUrl() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + final UUID returnedJob = UUID.randomUUID(); when(service.registerItemJob(any())).thenReturn(JobHandle.builder().id(returnedJob).build()); - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); this.mockMvc.perform(post("/irs/jobs").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( @@ -176,8 +175,9 @@ void shouldAcceptCorrectCallbackUrl() throws Exception { } @Test - @WithMockUser(authorities = "view_irs") void getJobsByState() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + final JobStatusResult returnedJob = JobStatusResult.builder() .id(UUID.randomUUID()) .state(JobState.COMPLETED) @@ -187,7 +187,6 @@ void getJobsByState() throws Exception { final String returnJobAsString = objectMapper.writeValueAsString(returnedJob); - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); when(service.getJobsByState(any(), any())).thenReturn( new PageResult(new PagedListHolder<>(List.of(returnedJob)))); @@ -205,20 +204,19 @@ void getJobsByState() throws Exception { } @Test - @WithMockUser(authorities = "view_irs") void cancelJobById() throws Exception { - final Job canceledJob = Job.builder().id(jobId).state(JobState.CANCELED).build(); + authenticateWith(IrsRoles.VIEW_IRS); - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); + final Job canceledJob = Job.builder().id(jobId).state(JobState.CANCELED).build(); when(this.service.cancelJobById(jobId)).thenReturn(canceledJob); this.mockMvc.perform(put("/irs/jobs/" + jobId)).andExpect(status().isOk()); } @Test - @WithMockUser(authorities = "view_irs") void cancelJobById_throwEntityNotFoundException() throws Exception { - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); + authenticateWith(IrsRoles.VIEW_IRS); + given(this.service.cancelJobById(jobId)).willThrow( new ResponseStatusException(HttpStatus.NOT_FOUND, "No job exists with id " + jobId)); @@ -228,18 +226,19 @@ void cancelJobById_throwEntityNotFoundException() throws Exception { } @Test - @WithMockUser(authorities = "view_irs") void getJobWithMalformedIdShouldReturnBadRequest() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + final String jobIdMalformed = UUID.randomUUID() + "MALFORMED"; this.mockMvc.perform(get("/irs/jobs/" + jobIdMalformed)).andExpect(status().isBadRequest()); } @Test - @WithMockUser(authorities = "view_irs") void shouldReturnBadRequestWhenRegisterJobWithMalformedAspectJson() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + when(service.registerItemJob(any())).thenThrow(IllegalArgumentException.class); - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); final String requestBody = "{ \"aspects\": [ \"MALFORMED\" ], \"globalAssetId\": \"urn:uuid:8a61c8db-561e-4db0-84ec-a693fc5ffdf6\" }"; this.mockMvc.perform(post("/irs/jobs").contentType(MediaType.APPLICATION_JSON).content(requestBody)) @@ -247,9 +246,9 @@ void shouldReturnBadRequestWhenRegisterJobWithMalformedAspectJson() throws Excep } @Test - @WithMockUser(authorities = "view_irs") void shouldReturnBadRequestWhenCancelingAlreadyCompletedJob() throws Exception { - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); + authenticateWith(IrsRoles.VIEW_IRS); + given(this.service.cancelJobById(jobId)).willThrow(new IllegalStateException( format("Cannot transition from state %s to %s", JobState.COMPLETED, JobState.CANCELED))); @@ -259,8 +258,9 @@ void shouldReturnBadRequestWhenCancelingAlreadyCompletedJob() throws Exception { } @Test - @WithMockUser(authorities = "view_irs") void shouldReturnAspectModels() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + final AspectModel assemblyPartRelationship = AspectModel.builder() .name("AssemblyPartRelationship") .urn("urn:bamm:io.catenax.assembly_part_relationship:1.1.1#AssemblyPartRelationship") @@ -274,7 +274,6 @@ void shouldReturnAspectModels() throws Exception { .models(List.of(assemblyPartRelationship)) .build(); - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); given(this.semanticHubService.getAllAspectModels()).willReturn(aspectModels); final String aspectModelResponseAsString = objectMapper.writeValueAsString(aspectModels); @@ -284,8 +283,9 @@ void shouldReturnAspectModels() throws Exception { } @Test - @WithMockUser(authorities = "view_irs_wrong_authority") void shouldReturnForbiddenStatusForAspectModelsWhenRequiredAuthorityIsMissing() throws Exception { + authenticateWith("view_irs_wrong_authority"); + this.mockMvc.perform(get("/irs/aspectmodels").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( registerJobWithoutDepthAndAspect()))) @@ -293,13 +293,13 @@ void shouldReturnForbiddenStatusForAspectModelsWhenRequiredAuthorityIsMissing() } @Test - @WithMockUser(authorities = "view_irs") void shouldReturnPartialWhenJobCompleted() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + final Jobs runningJob = Jobs.builder().job(Job.builder().id(jobId).state(JobState.RUNNING).build()).build(); final boolean shouldIncludePartial = Boolean.TRUE; final boolean shouldNotIncludePartial = Boolean.FALSE; - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); when(this.service.getJobForJobId(eq(jobId), anyBoolean())).thenReturn(runningJob); this.mockMvc.perform(get("/irs/jobs/" + jobId).queryParam("returnUncompletedJob", String.valueOf(shouldIncludePartial))).andExpect(status().isPartialContent()); @@ -307,13 +307,13 @@ void shouldReturnPartialWhenJobCompleted() throws Exception { } @Test - @WithMockUser(authorities = "view_irs") void shouldReturnOkWhenJobCompleted() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + final Jobs completedJob = Jobs.builder().job(Job.builder().id(jobId).state(JobState.COMPLETED).build()).build(); final boolean shouldIncludePartial = Boolean.TRUE; final boolean shouldNotIncludePartial = Boolean.FALSE; - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); when(this.service.getJobForJobId(eq(jobId), anyBoolean())).thenReturn(completedJob); this.mockMvc.perform(get("/irs/jobs/" + jobId).queryParam("returnUncompletedJob", String.valueOf(shouldIncludePartial))).andExpect(status().isOk()); diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/IrsExceptionHandlerTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/IrsExceptionHandlerTest.java index 59f061716c..8287dc8d6f 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/IrsExceptionHandlerTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/controllers/IrsExceptionHandlerTest.java @@ -32,9 +32,9 @@ import static org.springframework.web.client.HttpServerErrorException.InternalServerError; import com.fasterxml.jackson.databind.ObjectMapper; +import org.eclipse.tractusx.irs.ControllerTest; import org.eclipse.tractusx.irs.common.auth.IrsRoles; -import org.eclipse.tractusx.irs.configuration.SecurityConfiguration; -import org.eclipse.tractusx.irs.common.auth.AuthorizationService; +import org.eclipse.tractusx.irs.configuration.security.SecurityConfiguration; import org.eclipse.tractusx.irs.services.IrsItemGraphQueryService; import org.eclipse.tractusx.irs.services.SemanticHubService; import org.junit.jupiter.api.Test; @@ -44,29 +44,26 @@ import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; @WebMvcTest(IrsController.class) @Import(SecurityConfiguration.class) -class IrsExceptionHandlerTest { +class IrsExceptionHandlerTest extends ControllerTest { @MockBean private IrsItemGraphQueryService service; @MockBean private SemanticHubService semanticHubService; - @MockBean(name = "authorizationService") - private AuthorizationService authorizationService; @Autowired private MockMvc mockMvc; @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void handleAll() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + when(service.registerItemJob(any())).thenThrow(InternalServerError.class); - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); this.mockMvc.perform(post("/irs/jobs").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( @@ -75,10 +72,10 @@ void handleAll() throws Exception { } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldReturn500WhenGetSemanticModelsFails() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + when(semanticHubService.getAllAspectModels()).thenThrow(InternalServerError.class); - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); this.mockMvc.perform(get("/irs/aspectmodels").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( @@ -87,10 +84,10 @@ void shouldReturn500WhenGetSemanticModelsFails() throws Exception { } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldReturn400WhenProvidingBadInput() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + when(semanticHubService.getAllAspectModels()).thenThrow(IllegalArgumentException.class); - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); this.mockMvc.perform(get("/irs/aspectmodels").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( @@ -99,10 +96,10 @@ void shouldReturn400WhenProvidingBadInput() throws Exception { } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldReturn400WhenCatchingIllegalStateException() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + when(semanticHubService.getAllAspectModels()).thenThrow(IllegalStateException.class); - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); this.mockMvc.perform(get("/irs/aspectmodels").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( @@ -111,10 +108,10 @@ void shouldReturn400WhenCatchingIllegalStateException() throws Exception { } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldReturn400WhenCatchingMethodArgumentTypeMismatchException() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + when(semanticHubService.getAllAspectModels()).thenThrow(MethodArgumentTypeMismatchException.class); - when(authorizationService.verifyBpn()).thenReturn(Boolean.TRUE); this.mockMvc.perform(get("/irs/aspectmodels").contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( @@ -123,8 +120,9 @@ void shouldReturn400WhenCatchingMethodArgumentTypeMismatchException() throws Exc } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldReturn403WhenRightsAreMissing() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + when(semanticHubService.getAllAspectModels()).thenThrow(AccessDeniedException.class); this.mockMvc.perform(get("/irs/aspectmodels").contentType(MediaType.APPLICATION_JSON) diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/controller/EssControllerTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/controller/EssControllerTest.java index fd34ee0585..192f6600f5 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/controller/EssControllerTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/controller/EssControllerTest.java @@ -25,7 +25,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -34,23 +33,25 @@ import java.util.UUID; import com.fasterxml.jackson.databind.ObjectMapper; -import org.eclipse.tractusx.irs.ess.service.EssService; -import org.eclipse.tractusx.irs.common.auth.AuthorizationService; +import org.eclipse.tractusx.irs.ControllerTest; import org.eclipse.tractusx.irs.common.auth.IrsRoles; import org.eclipse.tractusx.irs.component.JobHandle; import org.eclipse.tractusx.irs.component.Jobs; import org.eclipse.tractusx.irs.component.PartChainIdentificationKey; import org.eclipse.tractusx.irs.component.RegisterBpnInvestigationJob; +import org.eclipse.tractusx.irs.configuration.security.SecurityConfiguration; +import org.eclipse.tractusx.irs.ess.service.EssService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; -import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(EssController.class) -class EssControllerTest { +@Import(SecurityConfiguration.class) +class EssControllerTest extends ControllerTest { @Autowired private MockMvc mockMvc; @@ -58,28 +59,26 @@ class EssControllerTest { @MockBean private EssService essService; - @MockBean(name = "authorizationService") - private AuthorizationService authorizationService; - private final String path = "/ess/bpn/investigations"; private final String globalAssetId = "urn:uuid:d3c0bf85-d44f-47c5-990d-fec8a36065c6"; private final String bpn = "BPNS000000000DDD"; @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldRegisterBpnInvestigationForValidRequest() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + when(essService.startIrsJob(any(RegisterBpnInvestigationJob.class))).thenReturn( JobHandle.builder().id(UUID.randomUUID()).build()); - this.mockMvc.perform(post(path).with(csrf()) - .contentType(MediaType.APPLICATION_JSON) + this.mockMvc.perform(post(path).contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( reqBody(globalAssetId, List.of(bpn))))).andExpect(status().isCreated()); } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldGetJob() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); + final String jobId = UUID.randomUUID().toString(); when(essService.getIrsJob(jobId)).thenReturn(Jobs.builder().build()); @@ -87,20 +86,20 @@ void shouldGetJob() throws Exception { } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldReturnBadRequestForWrongGlobalAssetId() throws Exception { - this.mockMvc.perform(post(path).with(csrf()) - .contentType(MediaType.APPLICATION_JSON) + authenticateWith(IrsRoles.VIEW_IRS); + + this.mockMvc.perform(post(path).contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( reqBody("wrongGlobalAssetId", List.of(bpn))))) .andExpect(status().isBadRequest()); } @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldReturnBadRequestForWrongBpn() throws Exception { - this.mockMvc.perform(post(path).with(csrf()) - .contentType(MediaType.APPLICATION_JSON) + authenticateWith(IrsRoles.VIEW_IRS); + + this.mockMvc.perform(post(path).contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString( reqBody(globalAssetId, List.of(bpn, "WRONG_BPN"))))) .andExpect(status().isBadRequest()); diff --git a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/controller/EssRecursiveControllerTest.java b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/controller/EssRecursiveControllerTest.java index fa74452107..7d08cde90f 100644 --- a/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/controller/EssRecursiveControllerTest.java +++ b/irs-api/src/test/java/org/eclipse/tractusx/irs/ess/controller/EssRecursiveControllerTest.java @@ -23,28 +23,30 @@ ********************************************************************************/ package org.eclipse.tractusx.irs.ess.controller; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.List; import com.fasterxml.jackson.databind.ObjectMapper; -import org.eclipse.tractusx.irs.ess.service.EssRecursiveService; +import org.eclipse.tractusx.irs.ControllerTest; import org.eclipse.tractusx.irs.common.auth.IrsRoles; +import org.eclipse.tractusx.irs.configuration.security.SecurityConfiguration; import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotification; import org.eclipse.tractusx.irs.edc.client.model.notification.EdcNotificationHeader; import org.eclipse.tractusx.irs.edc.client.model.notification.InvestigationNotificationContent; +import org.eclipse.tractusx.irs.ess.service.EssRecursiveService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; import org.springframework.http.MediaType; -import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(EssRecursiveController.class) -class EssRecursiveControllerTest { +@Import(SecurityConfiguration.class) +class EssRecursiveControllerTest extends ControllerTest { private final String path = "/ess/notification/receive-recursive"; @@ -55,11 +57,10 @@ class EssRecursiveControllerTest { private EssRecursiveService essRecursiveService; @Test - @WithMockUser(authorities = IrsRoles.VIEW_IRS) void shouldHandleRecursiveBpnInvestigationByNotification() throws Exception { + authenticateWith(IrsRoles.VIEW_IRS); - this.mockMvc.perform(post(path).with(csrf()) - .contentType(MediaType.APPLICATION_JSON) + this.mockMvc.perform(post(path).contentType(MediaType.APPLICATION_JSON) .content(new ObjectMapper().writeValueAsString(prepareNotification()))) .andExpect(status().isCreated()); } diff --git a/irs-common/src/test/java/org/eclipse/tractusx/irs/common/auth/AuthorizationServiceTest.java b/irs-common/src/test/java/org/eclipse/tractusx/irs/common/auth/AuthorizationServiceTest.java deleted file mode 100644 index 1cc89800f7..0000000000 --- a/irs-common/src/test/java/org/eclipse/tractusx/irs/common/auth/AuthorizationServiceTest.java +++ /dev/null @@ -1,112 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021,2022,2023 - * 2022: ZF Friedrichshafen AG - * 2022: ISTOS GmbH - * 2022,2023: Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - * 2022,2023: BOSCH AG - * Copyright (c) 2021,2022,2023 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.irs.common.auth; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.springframework.security.oauth2.jwt.JwtClaimNames.SUB; - -import java.time.Instant; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; - -class AuthorizationServiceTest { - - @Test - void shouldReturnTrueWhenTokenBpnIsEqualToAllowedBpn() { - // given - final String BPN = "BPNL00000001CRHK"; - final Map claims = Map.of(SUB, "sub", "clientId", "clientId", "bpn", BPN); - thereIsJwtAuthenticationWithClaims(claims); - final AuthorizationService authorizationService = new AuthorizationService(BPN); - - // when - final Boolean isBpnAllowed = authorizationService.verifyBpn(); - - // then - assertThat(isBpnAllowed).isEqualTo(Boolean.TRUE); - } - - @Test - void shouldReturnFalseWhenTokenBpnIsDifferentThanAllowedBpn() { - // given - final String claimBPN = "BPNL00000003CRHK"; - final String configurationBPN = "BPNL00000003CML1"; - final Map claims = Map.of(SUB, "sub", "clientId", "clientId", "bpn", claimBPN); - thereIsJwtAuthenticationWithClaims(claims); - final AuthorizationService authorizationService = new AuthorizationService(configurationBPN); - - // when - final Boolean isBpnAllowed = authorizationService.verifyBpn(); - - // then - assertThat(isBpnAllowed).isEqualTo(Boolean.FALSE); - } - - @Test - void shouldReturnFalseWhenNotAllowedBpnConfigured() { - // given - final String emptyConfigurationBPN = ""; - final AuthorizationService authorizationService = new AuthorizationService(emptyConfigurationBPN); - - // when - final Boolean isBpnAllowed = authorizationService.verifyBpn(); - - // then - assertThat(isBpnAllowed).isEqualTo(Boolean.FALSE); - } - - @Test - void shouldReturnFalseTokenBpnIsMissing() { - // given - final String configurationBPN = "BPNL00000003CML1"; - final Map claims = Map.of(SUB, "sub", "clientId", "clientId"); - thereIsJwtAuthenticationWithClaims(claims); - final AuthorizationService authorizationService = new AuthorizationService(configurationBPN); - - // when - final Boolean isBpnAllowed = authorizationService.verifyBpn(); - - // then - assertThat(isBpnAllowed).isEqualTo(Boolean.FALSE); - } - - private void thereIsJwtAuthenticationWithClaims(final Map claims) { - final JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwt(claims)); - SecurityContext securityContext = mock(SecurityContext.class); - Mockito.when(securityContext.getAuthentication()).thenReturn(jwtAuthenticationToken); - SecurityContextHolder.setContext(securityContext); - } - - Jwt jwt(final Map claims) { - return new Jwt("token", Instant.now(), Instant.now().plusSeconds(30), Map.of("alg", "none"), claims); - } - -} diff --git a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/controllers/PolicyStoreController.java b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/controllers/PolicyStoreController.java index efad6f94d8..ad18b47f6a 100644 --- a/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/controllers/PolicyStoreController.java +++ b/irs-policy-store/src/main/java/org/eclipse/tractusx/irs/policystore/controllers/PolicyStoreController.java @@ -99,7 +99,7 @@ public class PolicyStoreController { }) @PostMapping("/policies") @ResponseStatus(HttpStatus.CREATED) - @PreAuthorize("@authorizationService.verifyBpn() && hasAuthority('" + IrsRoles.ADMIN_IRS + "')") + @PreAuthorize("hasAuthority('" + IrsRoles.ADMIN_IRS + "')") public void registerAllowedPolicy(final @Valid @RequestBody CreatePolicyRequest request) { service.registerPolicy(request); } @@ -128,7 +128,7 @@ public void registerAllowedPolicy(final @Valid @RequestBody CreatePolicyRequest }) @GetMapping("/policies") @ResponseStatus(HttpStatus.OK) - @PreAuthorize("@authorizationService.verifyBpn() && hasAuthority('" + IrsRoles.ADMIN_IRS + "')") + @PreAuthorize("hasAuthority('" + IrsRoles.ADMIN_IRS + "')") public List getPolicies() { return service.getStoredPolicies(); } @@ -160,7 +160,7 @@ public List getPolicies() { }) @DeleteMapping("/policies/{policyId}") @ResponseStatus(HttpStatus.OK) - @PreAuthorize("@authorizationService.verifyBpn() && hasAuthority('" + IrsRoles.ADMIN_IRS + "')") + @PreAuthorize("hasAuthority('" + IrsRoles.ADMIN_IRS + "')") public void deleteAllowedPolicy(@PathVariable("policyId") final String policyId) { service.deletePolicy(policyId); } @@ -191,7 +191,7 @@ public void deleteAllowedPolicy(@PathVariable("policyId") final String policyId) }) @PutMapping("/policies/{policyId}") @ResponseStatus(HttpStatus.OK) - @PreAuthorize("@authorizationService.verifyBpn() && hasAuthority('" + IrsRoles.ADMIN_IRS + "')") + @PreAuthorize("hasAuthority('" + IrsRoles.ADMIN_IRS + "')") public void updateAllowedPolicy(@PathVariable("policyId") final String policyId, final @Valid @RequestBody UpdatePolicyRequest request) { service.updatePolicy(policyId, request);