From 55bc730a8bef631071505fc8d33273a17d9ed339 Mon Sep 17 00:00:00 2001 From: hamidonos Date: Tue, 3 Dec 2024 20:12:11 +0100 Subject: [PATCH] migrate web server to sechub server #3648 --- .../spring/LoginSecurityConfiguration.java | 219 --------- sechub-commons-security-spring/build.gradle | 1 + .../spring/security/AES256Encryption.java | 7 +- .../security/AES256EncryptionProperties.java | 43 -- .../AbstractSecurityConfiguration.java | 204 +++++++-- ...r.java => DynamicBearerTokenResolver.java} | 33 +- .../security/JwtDecoderConfiguration.java | 22 +- .../security/LoginClassicProperties.java | 11 - .../LoginClassicPropertiesConfiguration.java | 12 - .../security/LoginClassicSuccessHandler.java | 2 +- .../spring/security/LoginController.java | 49 ++ .../security/LoginEnabledCondition.java | 17 + .../security/LoginOAuth2Properties.java | 77 ---- .../LoginOAuth2PropertiesConfiguration.java | 13 - .../security/LoginOAuth2SuccessHandler.java | 11 +- .../spring/security/LoginProperties.java | 46 -- .../LoginPropertiesConfiguration.java | 12 - ...issingAuthenticationEntryPointHandler.java | 4 +- .../OAuth2JwtAuthenticationProvider.java | 7 +- .../spring/security/OAuth2JwtProperties.java | 21 - .../OAuth2JwtPropertiesConfiguration.java | 12 - ...Auth2OpaqueTokenIntrospectionResponse.java | 9 +- .../security/OAuth2OpaqueTokenProperties.java | 44 -- ...th2OpaqueTokenPropertiesConfiguration.java | 13 - .../spring}/security/PortAccessGuard.java | 12 +- .../spring/security/SecurityProperties.java | 333 ++++++++++++++ .../src/main/resources/static/favicon.ico | Bin 0 -> 4286 bytes .../src/main/resources/static/sechub-logo.svg | 0 .../src/main/resources/templates/login.html | 0 .../AES256EncryptionPropertiesTest.java | 63 --- .../spring/security/AES256EncryptionTest.java | 6 +- ...va => DynamicBearerTokenResolverTest.java} | 36 +- .../security/LoginOAuth2PropertiesTest.java | 89 ---- .../LoginOAuth2SuccessHandlerTest.java | 54 +-- .../security/OAuth2JwtIntegrationTest.java | 5 +- .../security/OAuth2JwtPropertiesTest.java | 46 -- .../OAuth2OpaqueTokenIntegrationTest.java | 4 +- .../OAuth2OpaqueTokenIntrospectorTest.java | 9 +- .../OAuth2OpaqueTokenPropertiesTest.java | 72 --- .../spring}/security/PortAccessGuardTest.java | 2 +- .../security/SecurityConfigurationTest.java | 2 +- .../security/SecurityPropertiesTest.java | 431 ++++++++++++++++++ .../TestOAuth2JwtSecurityConfiguration.java | 4 +- ...Auth2OpaqueTokenSecurityConfiguration.java | 12 +- .../security/TestSecurityConfiguration.java | 12 - .../security/TestSecurityController.java | 1 + .../application-aes256-encryption-test.yaml | 4 - .../test/resources/application-jwt-test.yml | 7 +- .../application-login-oauth2-test.yaml | 18 - .../resources/application-login-test.yaml | 15 + .../application-opaque-token-test.yml | 7 +- .../application-security-properties-test.yaml | 31 ++ .../TestRestDocSecurityConfiguration.java | 5 + sechub-server/.gitignore | 6 +- sechub-server/build.gradle | 2 +- .../src/main/resources/application-local.yml | 0 .../src/main/resources/application.yml | 47 +- sechub-shared-kernel/build.gradle | 1 - .../security/SecHubSecurityConfiguration.java | 13 - sechub-web-server/.gitignore | 2 - sechub-web-server/README.adoc | 41 -- sechub-web-server/README.md | 61 --- sechub-web-server/build.gradle | 95 ---- sechub-web-server/dev-base.sh | 28 -- .../dev-create_localhost_certificate.sh | 10 - .../dev-ensure_localhost_certificate.sh | 9 - .../sechub/webserver/ApplicationProfiles.java | 18 - .../webserver/SecHubWebServerApplication.java | 13 - .../server/ManagementServerProperties.java | 22 - .../webserver/server/ServerProperties.java | 22 - .../server/ServerPropertiesConfiguration.java | 11 - .../application-classic-auth-enabled.yml | 14 - .../application-integrationtest-data.yml | 6 - .../application-ssl-cert-provided.yml | 16 - .../application-ssl-cert-required.yml | 13 - .../src/main/resources/application.yml | 46 -- .../src/main/resources/banner.txt | 8 - .../certificates-untracked/.gitignore | 5 - .../certificates-untracked/README.md | 10 - .../main/resources/i18n/messages.properties | 11 - .../resources/i18n/messages_de.properties | 11 - .../src/main/resources/logback-spring.xml | 27 -- .../src/main/resources/static/css/main.css | 166 ------- .../resources/templates/fragments/banner.html | 13 - .../resources/templates/fragments/footer.html | 11 - .../resources/templates/fragments/header.html | 10 - .../resources/templates/fragments/navbar.html | 32 -- .../src/main/resources/templates/home.html | 47 -- .../resources/templates/new-apitoken.html | 20 - .../TestCookieRequestPostProcessor.java | 31 -- .../security/TestSecurityController.java | 63 --- .../TestWebServerSecurityConfiguration.java | 23 - .../WebServerSecurityConfigurationTest.java | 99 ---- .../src/test/resources/application-test.yml | 26 -- .../sechub/web/HomeController.java | 15 - .../sechub/web/LoginController.java | 39 -- .../sechub/web/RequestConstants.java | 12 - .../RequestParamLocaleContextResolver.java | 33 -- .../sechub/web/HomeControllerTest.java | 60 --- ...LoginControllerClassicAuthEnabledTest.java | 55 --- ...rollerOAuth2AndClassicAuthEnabledTest.java | 58 --- .../web/LoginControllerOAuth2EnabledTest.java | 59 --- 102 files changed, 1259 insertions(+), 2350 deletions(-) delete mode 100644 sechub-commons-security-login-spring/src/main/java/com/mercedesbenz/sechub/commons/security/login/spring/LoginSecurityConfiguration.java delete mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/AES256EncryptionProperties.java rename sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/{CookieAccessTokenResolver.java => DynamicBearerTokenResolver.java} (66%) delete mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginClassicProperties.java delete mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginClassicPropertiesConfiguration.java create mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginController.java create mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginEnabledCondition.java delete mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2Properties.java delete mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2PropertiesConfiguration.java delete mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginProperties.java delete mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginPropertiesConfiguration.java delete mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtProperties.java delete mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtPropertiesConfiguration.java delete mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenProperties.java delete mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenPropertiesConfiguration.java rename {sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver => sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring}/security/PortAccessGuard.java (74%) create mode 100644 sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/SecurityProperties.java create mode 100644 sechub-commons-security-spring/src/main/resources/static/favicon.ico rename {sechub-web-server => sechub-commons-security-spring}/src/main/resources/static/sechub-logo.svg (100%) rename {sechub-web-server => sechub-commons-security-spring}/src/main/resources/templates/login.html (100%) delete mode 100644 sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/AES256EncryptionPropertiesTest.java rename sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/{CookieAccessTokenResolverTest.java => DynamicBearerTokenResolverTest.java} (77%) delete mode 100644 sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2PropertiesTest.java delete mode 100644 sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtPropertiesTest.java delete mode 100644 sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenPropertiesTest.java rename {sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver => sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring}/security/PortAccessGuardTest.java (97%) create mode 100644 sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/SecurityPropertiesTest.java delete mode 100644 sechub-commons-security-spring/src/test/resources/application-aes256-encryption-test.yaml delete mode 100644 sechub-commons-security-spring/src/test/resources/application-login-oauth2-test.yaml create mode 100644 sechub-commons-security-spring/src/test/resources/application-login-test.yaml create mode 100644 sechub-commons-security-spring/src/test/resources/application-security-properties-test.yaml rename {sechub-web-server => sechub-server}/src/main/resources/application-local.yml (100%) delete mode 100644 sechub-web-server/.gitignore delete mode 100644 sechub-web-server/README.adoc delete mode 100644 sechub-web-server/README.md delete mode 100644 sechub-web-server/build.gradle delete mode 100644 sechub-web-server/dev-base.sh delete mode 100755 sechub-web-server/dev-create_localhost_certificate.sh delete mode 100755 sechub-web-server/dev-ensure_localhost_certificate.sh delete mode 100644 sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/ApplicationProfiles.java delete mode 100644 sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/SecHubWebServerApplication.java delete mode 100644 sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/server/ManagementServerProperties.java delete mode 100644 sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/server/ServerProperties.java delete mode 100644 sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/server/ServerPropertiesConfiguration.java delete mode 100644 sechub-web-server/src/main/resources/application-classic-auth-enabled.yml delete mode 100644 sechub-web-server/src/main/resources/application-integrationtest-data.yml delete mode 100644 sechub-web-server/src/main/resources/application-ssl-cert-provided.yml delete mode 100644 sechub-web-server/src/main/resources/application-ssl-cert-required.yml delete mode 100644 sechub-web-server/src/main/resources/application.yml delete mode 100644 sechub-web-server/src/main/resources/banner.txt delete mode 100644 sechub-web-server/src/main/resources/certificates-untracked/.gitignore delete mode 100644 sechub-web-server/src/main/resources/certificates-untracked/README.md delete mode 100644 sechub-web-server/src/main/resources/i18n/messages.properties delete mode 100644 sechub-web-server/src/main/resources/i18n/messages_de.properties delete mode 100644 sechub-web-server/src/main/resources/logback-spring.xml delete mode 100644 sechub-web-server/src/main/resources/static/css/main.css delete mode 100644 sechub-web-server/src/main/resources/templates/fragments/banner.html delete mode 100644 sechub-web-server/src/main/resources/templates/fragments/footer.html delete mode 100644 sechub-web-server/src/main/resources/templates/fragments/header.html delete mode 100644 sechub-web-server/src/main/resources/templates/fragments/navbar.html delete mode 100644 sechub-web-server/src/main/resources/templates/home.html delete mode 100644 sechub-web-server/src/main/resources/templates/new-apitoken.html delete mode 100644 sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/TestCookieRequestPostProcessor.java delete mode 100644 sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/TestSecurityController.java delete mode 100644 sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/TestWebServerSecurityConfiguration.java delete mode 100644 sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/WebServerSecurityConfigurationTest.java delete mode 100644 sechub-web-server/src/test/resources/application-test.yml delete mode 100644 sechub-web/src/main/java/com/mercedesbenz/sechub/web/HomeController.java delete mode 100644 sechub-web/src/main/java/com/mercedesbenz/sechub/web/LoginController.java delete mode 100644 sechub-web/src/main/java/com/mercedesbenz/sechub/web/RequestConstants.java delete mode 100644 sechub-web/src/main/java/com/mercedesbenz/sechub/web/RequestParamLocaleContextResolver.java delete mode 100644 sechub-web/src/test/java/com/mercedesbenz/sechub/web/HomeControllerTest.java delete mode 100644 sechub-web/src/test/java/com/mercedesbenz/sechub/web/LoginControllerClassicAuthEnabledTest.java delete mode 100644 sechub-web/src/test/java/com/mercedesbenz/sechub/web/LoginControllerOAuth2AndClassicAuthEnabledTest.java delete mode 100644 sechub-web/src/test/java/com/mercedesbenz/sechub/web/LoginControllerOAuth2EnabledTest.java diff --git a/sechub-commons-security-login-spring/src/main/java/com/mercedesbenz/sechub/commons/security/login/spring/LoginSecurityConfiguration.java b/sechub-commons-security-login-spring/src/main/java/com/mercedesbenz/sechub/commons/security/login/spring/LoginSecurityConfiguration.java deleted file mode 100644 index 9df2cbc9a8..0000000000 --- a/sechub-commons-security-login-spring/src/main/java/com/mercedesbenz/sechub/commons/security/login/spring/LoginSecurityConfiguration.java +++ /dev/null @@ -1,219 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.commons.security.login.spring; - -import java.util.Arrays; - -import com.mercedesbenz.sechub.spring.security.AES256Encryption; -import com.mercedesbenz.sechub.spring.security.LoginOAuth2Properties; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.core.annotation.Order; -import org.springframework.core.env.Environment; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.context.SecurityContextHolderFilter; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.NegatedRequestMatcher; -import org.springframework.security.web.util.matcher.OrRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.web.client.RestTemplate; - -import com.mercedesbenz.sechub.webserver.ApplicationProfiles; -import com.mercedesbenz.sechub.webserver.RequestConstants; -import com.mercedesbenz.sechub.webserver.encryption.AES256Encryption; -import com.mercedesbenz.sechub.webserver.server.ManagementServerProperties; -import com.mercedesbenz.sechub.webserver.server.ServerProperties; - -@Configuration -@EnableWebSecurity -@EnableMethodSecurity -class LoginSecurityConfiguration { - - static final String ACCESS_TOKEN = "access_token"; - static final String LOGIN_PROPERTIES_PREFIX = "sechub.security.login"; - - private static final String ACTUATOR_PATH = "/actuator/**"; - /* @formatter:off */ - private static final String[] PUBLIC_PATHS = { - RequestConstants.LOGIN, - "/login/**", - "/css/**", - "/js/**", - "/images/**", - "/oauth2/**", - "/sechub-logo.svg" - }; - /* @formatter:on */ - private static final String SCOPE = "openid"; - private static final String USER_NAME_ATTRIBUTE_NAME = "sub"; - - private final Environment environment; - private final LoginOAuth2Properties loginOAuth2Properties; - private final AES256Encryption aes256Encryption; - - /* @formatter:off */ - LoginSecurityConfiguration(@Autowired Environment environment, - @Autowired(required = false) LoginOAuth2Properties loginOAuth2Properties, - @Autowired AES256Encryption aes256Encryption) { - /* @formatter:on */ - this.environment = environment; - if (isOAuth2Enabled() && loginOAuth2Properties == null) { - throw new NoSuchBeanDefinitionException(LoginOAuth2Properties.class); - } - if (!isOAuth2Enabled() && !isClassicAuthEnabled()) { - throw new IllegalStateException("At least one authentication method must be enabled"); - } - this.loginOAuth2Properties = loginOAuth2Properties; - this.aes256Encryption = aes256Encryption; - } - - @Bean - @Profile(ApplicationProfiles.OAUTH2_ENABLED) - ClientRegistrationRepository clientRegistrationRepository() { - /* @formatter:off */ - ClientRegistration clientRegistration = ClientRegistration - .withRegistrationId(loginOAuth2Properties.getProvider()) - .clientId(loginOAuth2Properties.getClientId()) - .clientSecret(loginOAuth2Properties.getClientSecret()) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .redirectUri(loginOAuth2Properties.getRedirectUri()) - .issuerUri(loginOAuth2Properties.getIssuerUri()).scope(SCOPE) - .authorizationUri(loginOAuth2Properties.getAuthorizationUri()) - .tokenUri(loginOAuth2Properties.getTokenUri()) - .userInfoUri(loginOAuth2Properties.getUserInfoUri()) - .userNameAttributeName(USER_NAME_ATTRIBUTE_NAME) - .build(); - /* @formatter:on */ - - return new InMemoryClientRegistrationRepository(clientRegistration); - } - - @Bean - @Order(1) - /* @formatter:off */ - SecurityFilterChain securityFilterChainActuator(HttpSecurity httpSecurity, - ManagementServerProperties managementServerProperties) throws Exception { - PortAccessGuard portAccessGuard = new PortAccessGuard(managementServerProperties.getPort()); - - httpSecurity - .securityMatcher(ACTUATOR_PATH) - .authorizeHttpRequests(authorizeRequests -> authorizeRequests - .requestMatchers(ACTUATOR_PATH) - .permitAll()) - .addFilterBefore(portAccessGuard, SecurityContextHolderFilter.class); - /* @formatter:on */ - return httpSecurity.build(); - } - - @Bean - @Profile(ApplicationProfiles.OAUTH2_ENABLED) - @Order(2) - /* @formatter:off */ - SecurityFilterChain securityFilterChainProtectedPaths(HttpSecurity httpSecurity, - @Autowired(required = false) AuthenticationManager authenticationManager, - ServerProperties serverProperties) throws Exception { - AuthenticationEntryPoint authenticationEntryPoint = new MissingAuthenticationEntryPointHandler(); - BearerTokenResolver bearerTokenResolver = new JwtCookieResolver(aes256Encryption); - RequestMatcher publicPathsMatcher = new OrRequestMatcher( - Arrays.stream(PUBLIC_PATHS) - .map(AntPathRequestMatcher::new) - .toArray(AntPathRequestMatcher[]::new) - ); - RequestMatcher protectedPathsMatcher = new NegatedRequestMatcher(publicPathsMatcher); - PortAccessGuard portAccessGuard = new PortAccessGuard(serverProperties.getPort()); - - httpSecurity - .securityMatcher(protectedPathsMatcher) - .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer - .authenticationEntryPoint(authenticationEntryPoint) - .bearerTokenResolver(bearerTokenResolver) - .jwt(jwt -> jwt.jwkSetUri(loginOAuth2Properties.getJwkSetUri())) - ); - /* @formatter:on */ - - if (authenticationManager != null) { - /* - * This is useful to mock authentication when no real authentication manager can - * be constructed (e.g. in tests) - */ - httpSecurity.authenticationManager(authenticationManager); - } - - httpSecurity.addFilterBefore(portAccessGuard, SecurityContextHolderFilter.class); - - return httpSecurity.build(); - } - - @Bean - @Order(3) - /* @formatter:on */ - SecurityFilterChain securityFilterChainPublicPaths(HttpSecurity httpSecurity, - @Autowired(required = false) OAuth2AuthorizedClientService oAuth2AuthorizedClientService, ServerProperties serverProperties) throws Exception { - - PortAccessGuard portAccessGuard = new PortAccessGuard(serverProperties.getPort()); - - httpSecurity.securityMatcher(PUBLIC_PATHS) - /* Disable CSRF */ - .csrf(AbstractHttpConfigurer::disable) - /* Make the application stateless */ - .sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); - - if (isOAuth2Enabled()) { - RestTemplate restTemplate = new RestTemplate(); - Base64EncodedClientIdAndSecretOAuth2AccessTokenClient base64EncodedClientIdAndSecretOAuth2AccessTokenClient = new Base64EncodedClientIdAndSecretOAuth2AccessTokenClient( - restTemplate); - if (oAuth2AuthorizedClientService == null) { - throw new NoSuchBeanDefinitionException( - "No qualifying bean of type 'OAuth2AuthorizedClientService' available: expected at least 1 bean which qualifies as autowire candidate."); - } - AuthenticationSuccessHandler authenticationSuccessHandler = new OAuth2LoginSuccessHandler(loginOAuth2Properties, oAuth2AuthorizedClientService, - aes256Encryption); - /* Enable OAuth2 */ - httpSecurity.oauth2Login(oauth2 -> oauth2.loginPage(RequestConstants.LOGIN) - .tokenEndpoint(token -> token.accessTokenResponseClient(base64EncodedClientIdAndSecretOAuth2AccessTokenClient)) - .successHandler(authenticationSuccessHandler)); - } - - if (isClassicAuthEnabled()) { - /* - * Enable Classic Authentication Note: This must be the last configuration in - * order to set the default 'loginPage' to oAuth2 because spring uses the - * 'loginPage' from the first authentication method configured - */ - AuthenticationSuccessHandler authenticationSuccessHandler = new ClassicLoginSuccessHandler(); - httpSecurity.formLogin(form -> form.loginPage(RequestConstants.LOGIN).successHandler(authenticationSuccessHandler)); - } - - /* @formatter:on */ - - httpSecurity.addFilterBefore(portAccessGuard, SecurityContextHolderFilter.class); - - return httpSecurity.build(); - } - - private boolean isOAuth2Enabled() { - return environment.matchesProfiles(ApplicationProfiles.OAUTH2_ENABLED); - } - - private boolean isClassicAuthEnabled() { - return environment.matchesProfiles(ApplicationProfiles.CLASSIC_AUTH_ENABLED); - } - -} diff --git a/sechub-commons-security-spring/build.gradle b/sechub-commons-security-spring/build.gradle index 1da7a629bd..753bbcbbf1 100644 --- a/sechub-commons-security-spring/build.gradle +++ b/sechub-commons-security-spring/build.gradle @@ -9,6 +9,7 @@ dependencies { implementation project(':sechub-commons-core') implementation project(':sechub-testframework-spring') + implementation library.springboot_starter_web implementation library.springboot_starter_security implementation library.springboot_starter_oauth2_client implementation library.springboot_starter_oauth2_resource_server diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/AES256Encryption.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/AES256Encryption.java index 53214a3c35..2350d39a24 100644 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/AES256Encryption.java +++ b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/AES256Encryption.java @@ -10,14 +10,11 @@ import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Component; import com.mercedesbenz.sechub.commons.core.security.CryptoAccess; -// TODO: move to a central place @Component -@EnableConfigurationProperties(AES256EncryptionProperties.class) public class AES256Encryption { private static final String TRANSFORMATION = "AES"; @@ -27,8 +24,8 @@ public class AES256Encryption { private final Cipher decrypt; private final SealedObject sealedSecretKey; - AES256Encryption(AES256EncryptionProperties properties) throws GeneralSecurityException { - SecretKey secretKey = new SecretKeySpec(properties.getSecretKeyBytes(), TRANSFORMATION); + AES256Encryption(SecurityProperties securityProperties) throws GeneralSecurityException { + SecretKey secretKey = new SecretKeySpec(securityProperties.getEncryption().getSecretKey().getBytes(StandardCharsets.UTF_8), TRANSFORMATION); this.sealedSecretKey = secretKeyCryptoAccess.seal(secretKey); this.encrypt = Cipher.getInstance(TRANSFORMATION); diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/AES256EncryptionProperties.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/AES256EncryptionProperties.java deleted file mode 100644 index fcad58bdaa..0000000000 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/AES256EncryptionProperties.java +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.spring.security; - -import static java.util.Objects.requireNonNull; - -import java.nio.charset.StandardCharsets; - -import javax.crypto.SealedObject; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.bind.ConstructorBinding; - -import com.mercedesbenz.sechub.commons.core.security.CryptoAccess; - -@ConfigurationProperties(prefix = AES256EncryptionProperties.PREFIX) -class AES256EncryptionProperties { - - static final String PREFIX = "sechub.security.encryption"; - private static final String ERR_MSG_FORMAT = "The property '%s.%s' must not be null"; - private static final int AES_256_SECRET_KEY_LENGTH = 32; - - private final SealedObject secretKeySealed; - - @ConstructorBinding - AES256EncryptionProperties(String secretKey) { - requireNonNull(secretKey, ERR_MSG_FORMAT.formatted(PREFIX, "secret-key")); - this.secretKeySealed = CryptoAccess.CRYPTO_STRING.seal(secretKey); - if (!is256BitString(secretKey)) { - throw new IllegalArgumentException("The property %s.%s must be a 256-bit string".formatted(PREFIX, "secret-key")); - } - } - - byte[] getSecretKeyBytes() { - return CryptoAccess.CRYPTO_STRING.unseal(secretKeySealed).getBytes(StandardCharsets.UTF_8); - } - - /* - * Checks if the secret key length is 32 characters (32 * 8 = 256 bits) - */ - private static boolean is256BitString(String secretKey) { - return secretKey.length() == AES_256_SECRET_KEY_LENGTH; - } -} diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/AbstractSecurityConfiguration.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/AbstractSecurityConfiguration.java index f310f7151d..fe97bc8269 100644 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/AbstractSecurityConfiguration.java +++ b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/AbstractSecurityConfiguration.java @@ -1,10 +1,16 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.spring.security; +import java.util.HashSet; +import java.util.Set; + import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -12,11 +18,20 @@ import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; -import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.NegatedRequestMatcher; +import org.springframework.security.web.util.matcher.OrRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.web.client.RestTemplate; /** @@ -34,20 +49,6 @@ * authorization. *

* - *

- * To enable OAuth2 authentication in JWT mode, you must set the following - * property {@code sechub.security.oauth2.jwt.enabled=true} in the application - * properties. For opaque token mode, set - * {@code sechub.security.oauth2.opaque-token.enabled=true}. Note: This - * configuration requires exactly one of the two modes to be enabled. - *

- * - *

- * Subclasses must implement the {@link #isOAuth2Enabled()} method to indicate - * whether OAuth2 is enabled and the {@link #authorizeHttpRequests()} method to - * configure API access permissions. - *

- * * @see org.springframework.security.config.annotation.web.builders.HttpSecurity * @see org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer * @see org.springframework.security.oauth2.jwt.JwtDecoder @@ -57,70 +58,114 @@ * * @author hamidonos */ +@EnableConfigurationProperties(SecurityProperties.class) public abstract class AbstractSecurityConfiguration { - static final String ACCESS_TOKEN = "access_token"; - static final String LOGIN_PROPERTIES_PREFIX = "sechub.security.login"; + static final String ACCESS_TOKEN = "access_token"; static final String SERVER_OAUTH2_PROPERTIES_PREFIX = "sechub.security.server.oauth2";; static final String MODE = "mode"; + private static final String SCOPE = "openid"; + private static final String SUBJECT = "sub"; + /* @formatter:off */ + private static final Set DEFAULT_PUBLIC_PATHS = Set.of( + "/css/**", + "/js/**", + "/images/**", + "/login/oauth2/**", + "/oauth2/**", + "/favicon.ico", + "/sechub-logo.svg" + ); + /* @formatter:on */ + /* @formatter:off */ @Bean - SecurityFilterChain filterChain(HttpSecurity httpSecurity, - @Autowired(required = false) UserDetailsService userDetailsService, - RestTemplate restTemplate, - @Autowired(required = false) OAuth2JwtProperties OAuth2JwtProperties, - @Autowired(required = false) OAuth2OpaqueTokenProperties oAuth2OpaqueTokenProperties, - @Autowired(required = false) JwtDecoder jwtDecoder) throws Exception { - - httpSecurity.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + @Order(1) + SecurityFilterChain securityFilterChainResourceServer(HttpSecurity httpSecurity, + SecurityProperties securityProperties, + @Autowired(required = false) UserDetailsService userDetailsService, + RestTemplate restTemplate, + @Autowired(required = false) AES256Encryption aes256Encryption, + @Autowired(required = false) JwtDecoder jwtDecoder) throws Exception { + SecurityProperties.Login login = securityProperties.getLogin(); + + if (login != null && login.isEnabled()) { + Set publicPaths = new HashSet<>(DEFAULT_PUBLIC_PATHS); + publicPaths.add(login.getLoginPage()); + + RequestMatcher publicPathsMatcher = new OrRequestMatcher( + publicPaths.stream() + .map(AntPathRequestMatcher::new) + .toArray(AntPathRequestMatcher[]::new)); + + RequestMatcher protectedPathsMatcher = new NegatedRequestMatcher(publicPathsMatcher); + + httpSecurity.securityMatcher(protectedPathsMatcher); + } + + httpSecurity + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorizeHttpRequests()) .csrf(AbstractHttpConfigurer::disable) // CSRF protection disabled. The CookieServerCsrfTokenRepository does // not work since Spring Boot 3 .httpBasic(Customizer.withDefaults()).headers((headers) -> headers .contentSecurityPolicy((csp) -> csp.policyDirectives("default-src 'none'; style-src 'unsafe-inline'"))); - if (isOAuth2Enabled()) { + SecurityProperties.Server server = securityProperties.getServer(); + + if (server != null && server.isOAuth2ModeEnabled()) { if (userDetailsService == null) { throw new NoSuchBeanDefinitionException(UserDetailsService.class); } - if ((OAuth2JwtProperties == null && oAuth2OpaqueTokenProperties == null) || (OAuth2JwtProperties != null && oAuth2OpaqueTokenProperties != null)) { - String exMsg = "Either JWT or opaque token mode must be enabled by setting the '%s.%s' property to either '%s' or '%s'".formatted( + SecurityProperties.Server.OAuth2 oAuth2 = server.getOAuth2(); + + if (oAuth2.isJwtModeEnabled() == oAuth2.isOpaqueTokenModeEnabled()) { + String exMsg = "Either 'jwt' or opaque token mode must be enabled by setting the '%s.%s' property to either '%s' or '%s'".formatted( SERVER_OAUTH2_PROPERTIES_PREFIX, MODE, - OAuth2JwtPropertiesConfiguration.MODE, - OAuth2OpaqueTokenPropertiesConfiguration.MODE + SecurityProperties.Server.OAuth2.OAUTH2_JWT_MODE, + SecurityProperties.Server.OAuth2.OAUTH2_OPAQUE_TOKEN_MODE ); throw new BeanInstantiationException(SecurityFilterChain.class, exMsg); } - if (OAuth2JwtProperties != null) { + if (aes256Encryption == null) { + throw new NoSuchBeanDefinitionException(AES256Encryption.class); + } + + BearerTokenResolver bearerTokenResolver = new DynamicBearerTokenResolver(aes256Encryption); + + if (oAuth2.isJwtModeEnabled()) { if (jwtDecoder == null) { throw new NoSuchBeanDefinitionException(JwtDecoder.class); } - BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver(); AuthenticationProvider authenticationProvider = new OAuth2JwtAuthenticationProvider(userDetailsService, jwtDecoder); httpSecurity .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer - .jwt(jwt -> jwt.decoder(jwtDecoder)) - .bearerTokenResolver(bearerTokenResolver)) + .jwt(jwt -> jwt.decoder(jwtDecoder)) + .bearerTokenResolver(bearerTokenResolver) + ) .authenticationProvider(authenticationProvider); } - if (oAuth2OpaqueTokenProperties != null) { + if (oAuth2.isOpaqueTokenModeEnabled()) { + SecurityProperties.Server.OAuth2.OpaqueToken opaqueToken = oAuth2.getOpaqueToken(); OpaqueTokenIntrospector opaqueTokenIntrospector = new OAuth2OpaqueTokenIntrospector( restTemplate, - oAuth2OpaqueTokenProperties.getIntrospectionUri(), - oAuth2OpaqueTokenProperties.getClientId(), - oAuth2OpaqueTokenProperties.getClientSecret(), + opaqueToken.getIntrospectionUri(), + opaqueToken.getClientId(), + opaqueToken.getClientSecret(), userDetailsService); httpSecurity .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer - .opaqueToken(opaqueToken -> opaqueToken.introspector(opaqueTokenIntrospector))); + .opaqueToken(opaqueTokenConfigurer -> opaqueTokenConfigurer.introspector(opaqueTokenIntrospector)) + .bearerTokenResolver(bearerTokenResolver) + ); } } /* @formatter:on */ @@ -128,7 +173,86 @@ SecurityFilterChain filterChain(HttpSecurity httpSecurity, return httpSecurity.build(); } - protected abstract boolean isOAuth2Enabled(); + @Bean + @Conditional(LoginEnabledCondition.class) + ClientRegistrationRepository clientRegistrationRepository(SecurityProperties securityProperties) { + SecurityProperties.Login login = securityProperties.getLogin(); + SecurityProperties.Login.OAuth2 oAuth2 = login.getOAuth2(); + + /* @formatter:off */ + ClientRegistration clientRegistration = ClientRegistration + .withRegistrationId(oAuth2.getProvider()) + .clientId(oAuth2.getClientId()) + .clientSecret(oAuth2.getClientSecret()) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .redirectUri(oAuth2.getRedirectUri()) + .issuerUri(oAuth2.getIssuerUri()) + .scope(SCOPE) + .authorizationUri(oAuth2.getAuthorizationUri()) + .tokenUri(oAuth2.getTokenUri()) + .userInfoUri(oAuth2.getUserInfoUri()) + .userNameAttributeName(SUBJECT) + .jwkSetUri(oAuth2.getJwkSetUri()) + .build(); + /* @formatter:on */ + + return new InMemoryClientRegistrationRepository(clientRegistration); + } + + @Bean + @Conditional(LoginEnabledCondition.class) + @Order(2) + /* @formatter:off */ + SecurityFilterChain securityFilterChainLogin(HttpSecurity httpSecurity, + @Autowired(required = false) SecurityProperties securityProperties, + @Autowired(required = false) AES256Encryption aes256Encryption, + @Autowired(required = false) OAuth2AuthorizedClientService oAuth2AuthorizedClientService) throws Exception { + SecurityProperties.Login login = securityProperties.getLogin(); + + Set publicPaths = new HashSet<>(DEFAULT_PUBLIC_PATHS); + publicPaths.add(login.getLoginPage()); + + httpSecurity.securityMatcher(publicPaths.toArray(new String[0])) + /* Disable CSRF */ + .csrf(AbstractHttpConfigurer::disable) + /* Make the application stateless */ + .sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + + if (login.isOAuth2ModeEnabled()) { + SecurityProperties.Login.OAuth2 loginOAuth2 = login.getOAuth2(); + RestTemplate restTemplate = new RestTemplate(); + LoginOAuth2AccessTokenClient loginOAuth2AccessTokenClient = new LoginOAuth2AccessTokenClient(restTemplate); + if (oAuth2AuthorizedClientService == null) { + throw new NoSuchBeanDefinitionException(OAuth2AuthorizedClientService.class); + } + if (aes256Encryption == null) { + throw new NoSuchBeanDefinitionException(AES256Encryption.class); + } + AuthenticationSuccessHandler authenticationSuccessHandler = new LoginOAuth2SuccessHandler(loginOAuth2.getProvider(), oAuth2AuthorizedClientService, + aes256Encryption, login.getRedirectUri()); + /* Enable OAuth2 */ + httpSecurity.oauth2Login(oauth2 -> oauth2.loginPage(login.getLoginPage()) + .tokenEndpoint(token -> token.accessTokenResponseClient(loginOAuth2AccessTokenClient)) + .successHandler(authenticationSuccessHandler)); + } + + if (login.isClassicModeEnabled()) { + /* + * Enable Classic Authentication + * + * Note: This must be the last configuration in + * order to set the default 'loginPage' to oAuth2 because spring uses the + * 'loginPage' from the first authentication method configured + */ + AuthenticationSuccessHandler authenticationSuccessHandler = new LoginClassicSuccessHandler(login.getRedirectUri()); + httpSecurity.formLogin(form -> form.loginPage(login.getLoginPage()).successHandler(authenticationSuccessHandler)); + } + + /* @formatter:on */ + + return httpSecurity.build(); + } protected abstract Customizer.AuthorizationManagerRequestMatcherRegistry> authorizeHttpRequests(); diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/CookieAccessTokenResolver.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/DynamicBearerTokenResolver.java similarity index 66% rename from sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/CookieAccessTokenResolver.java rename to sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/DynamicBearerTokenResolver.java index 3686d4036c..ed9f6b70d4 100644 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/CookieAccessTokenResolver.java +++ b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/DynamicBearerTokenResolver.java @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.spring.security; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; + import java.util.Arrays; import java.util.Base64; @@ -12,30 +14,47 @@ import jakarta.servlet.http.HttpServletRequest; /** - * {@code CookieTokenResolver} implements {@link BearerTokenResolver} to provide - * custom Bearer Token resolution. The encrypted access token is read from the - * cookies and decrypted using {@link AES256Encryption}. Note that the access - * token is expected in {@link Base64} encoded format. + * This class implements the {@link BearerTokenResolver} interface to provide + * custom Bearer Token resolution. The access token is read from the + * Authorization header first. If the access token is not found in + * the header, it is then read from the cookies and decrypted using + * {@link AES256Encryption}. Note that the access token has to be encrypted and + * encoded in {@link Base64} format when passed as a cookie. * * @see BearerTokenResolver * @see AES256Encryption * * @author hamidonos */ -class CookieAccessTokenResolver implements BearerTokenResolver { +class DynamicBearerTokenResolver implements BearerTokenResolver { - private static final Logger LOG = LoggerFactory.getLogger(CookieAccessTokenResolver.class); + private static final Logger LOG = LoggerFactory.getLogger(DynamicBearerTokenResolver.class); private static final String MISSING_ACCESS_TOKEN_VALUE = "missing-access-token"; private static final Base64.Decoder DECODER = Base64.getDecoder(); + private static final String BEARER_PREFIX = "Bearer "; private final AES256Encryption aes256Encryption; - CookieAccessTokenResolver(AES256Encryption aes256Encryption) { + DynamicBearerTokenResolver(AES256Encryption aes256Encryption) { this.aes256Encryption = aes256Encryption; } @Override public String resolve(HttpServletRequest request) { + + /* + * Try to resolve the token from the 'Authorization' header first + */ + + String authHeader = request.getHeader(AUTHORIZATION); + if (authHeader != null && authHeader.startsWith(BEARER_PREFIX)) { + return authHeader.substring(BEARER_PREFIX.length()); + } + + /* + * Attempt to resolve the token from the 'access_token' cookie + */ + Cookie[] cookies = request.getCookies(); if (cookies == null) { diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/JwtDecoderConfiguration.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/JwtDecoderConfiguration.java index 5e57453e15..ee675874c9 100644 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/JwtDecoderConfiguration.java +++ b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/JwtDecoderConfiguration.java @@ -1,26 +1,40 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.spring.security; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import static java.util.Objects.requireNonNull; + import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; @Configuration -@ConditionalOnProperty(value = "sechub.security.oauth2.token-type", havingValue = "JWT") +@Conditional(JwtDecoderConfiguration.JwtModeEnabledCondition.class) class JwtDecoderConfiguration { @Bean - JwtDecoder jwtDecoder(OAuth2JwtProperties oAuth2JwtProperties) { + JwtDecoder jwtDecoder(SecurityProperties securityProperties) { /* * @formatter:off * The `NimbusJwtDecoder` is a `JwtDecoder` implementation that utilizes the Nimbus JOSE + JWT library to decode JSON Web Tokens (JWTs). * It requires a JWK Set URI to fetch the public keys from the Identity Provider (IDP) to verify the JWT's signature. */ return NimbusJwtDecoder - .withJwkSetUri(oAuth2JwtProperties.getJwkSetUri()) + .withJwkSetUri(securityProperties.getServer().getOAuth2().getJwt().getJwkSetUri()) .build(); /* @formatter:on */ } + + static class JwtModeEnabledCondition implements Condition { + @Override + public boolean matches(ConditionContext context, @SuppressWarnings("NullableProblems") AnnotatedTypeMetadata metadata) { + String modePrefix = SecurityProperties.Server.OAuth2.PREFIX + ".mode"; + String property = context.getEnvironment().getProperty(modePrefix); + return SecurityProperties.Server.OAuth2.OAUTH2_JWT_MODE.equals(requireNonNull(property, "Property %s must not be null".formatted(modePrefix))); + } + } } diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginClassicProperties.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginClassicProperties.java deleted file mode 100644 index 6506321036..0000000000 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginClassicProperties.java +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.spring.security; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = LoginClassicProperties.PREFIX) -public class LoginClassicProperties { - - static final String PREFIX = "sechub.security.login.classic"; - -} diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginClassicPropertiesConfiguration.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginClassicPropertiesConfiguration.java deleted file mode 100644 index 0450c3d7fa..0000000000 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginClassicPropertiesConfiguration.java +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.spring.security; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties(LoginClassicProperties.class) -@ConditionalOnProperty(prefix = AbstractSecurityConfiguration.LOGIN_PROPERTIES_PREFIX, name = "enabled", havingValue = "true") -class LoginClassicPropertiesConfiguration { -} \ No newline at end of file diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginClassicSuccessHandler.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginClassicSuccessHandler.java index dd20142677..6587670259 100644 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginClassicSuccessHandler.java +++ b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginClassicSuccessHandler.java @@ -17,7 +17,7 @@ * successful authentication. This handler redirects the user to the specified * redirectUri. * - * @see LoginSecurityConfiguration + * @see AbstractSecurityConfiguration * * @author hamidonos */ diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginController.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginController.java new file mode 100644 index 0000000000..40b29a8e67 --- /dev/null +++ b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginController.java @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.spring.security; + +import org.springframework.context.annotation.Conditional; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +@Controller +@Conditional(LoginEnabledCondition.class) +class LoginController { + + private final SecurityProperties.Login login; + private final boolean isOAuth2Enabled; + private final boolean isClassicAuthEnabled; + + /* @formatter:off */ + LoginController(RequestMappingHandlerMapping requestMappingHandlerMapping, + SecurityProperties securityProperties) throws NoSuchMethodException { + login = securityProperties.getLogin(); + /* register the login page dynamically at runtime */ + registerLoginMapping(requestMappingHandlerMapping, login.getLoginPage()); + this.isOAuth2Enabled = login.isOAuth2ModeEnabled(); + this.isClassicAuthEnabled = login.isClassicModeEnabled(); + } + /* @formatter:on */ + + String login(Model model) { + model.addAttribute("isOAuth2Enabled", isOAuth2Enabled); + model.addAttribute("isClassicAuthEnabled", isClassicAuthEnabled); + + if (login != null) { + String registrationId = login.getOAuth2().getProvider(); + model.addAttribute("registrationId", registrationId); + } + + return "login"; + } + + private void registerLoginMapping(RequestMappingHandlerMapping requestMappingHandlerMapping, String loginPage) throws NoSuchMethodException { + RequestMappingInfo requestMappingInfo = RequestMappingInfo.paths(loginPage).methods(RequestMethod.GET).produces(MediaType.APPLICATION_JSON_VALUE) + .build(); + + requestMappingHandlerMapping.registerMapping(requestMappingInfo, this, getClass().getDeclaredMethod("login", Model.class)); + } +} \ No newline at end of file diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginEnabledCondition.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginEnabledCondition.java new file mode 100644 index 0000000000..3d8bcd9720 --- /dev/null +++ b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginEnabledCondition.java @@ -0,0 +1,17 @@ +package com.mercedesbenz.sechub.spring.security; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class LoginEnabledCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, @SuppressWarnings("NullableProblems") AnnotatedTypeMetadata metadata) { + /* @formatter:off */ + return context + .getEnvironment() + .getProperty("%s.enabled".formatted(SecurityProperties.Login.PREFIX), boolean.class, false); + /* @formatter:on */ + } +} \ No newline at end of file diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2Properties.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2Properties.java deleted file mode 100644 index f5dd0feab6..0000000000 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2Properties.java +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.spring.security; - -import static java.util.Objects.requireNonNull; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.bind.ConstructorBinding; - -@ConfigurationProperties(prefix = LoginOAuth2Properties.PREFIX) -public final class LoginOAuth2Properties { - - static final String PREFIX = "sechub.security.login.oauth2"; - private static final String ERR_MSG_FORMAT = "The property '%s.%s' must not be null"; - - private final String clientId; - private final String clientSecret; - private final String provider; - private final String redirectUri; - private final String issuerUri; - private final String authorizationUri; - private final String tokenUri; - private final String userInfoUri; - - /* @formatter:off */ - @ConstructorBinding - LoginOAuth2Properties(String clientId, - String clientSecret, - String provider, - String redirectUri, - String issuerUri, - String authorizationUri, - String tokenUri, - String userInfoUri) { - this.clientId = requireNonNull(clientId, ERR_MSG_FORMAT.formatted(PREFIX, "client-id")); - this.clientSecret = requireNonNull(clientSecret, ERR_MSG_FORMAT.formatted(PREFIX, "client-secret"));; - this.provider = requireNonNull(provider, ERR_MSG_FORMAT.formatted(PREFIX, "provider")); - this.redirectUri = requireNonNull(redirectUri, ERR_MSG_FORMAT.formatted(PREFIX, "redirect-uri")); - this.issuerUri = requireNonNull(issuerUri, ERR_MSG_FORMAT.formatted(PREFIX, "issuer-uri")); - this.authorizationUri = requireNonNull(authorizationUri, ERR_MSG_FORMAT.formatted(PREFIX, "authorization-uri")); - this.tokenUri = requireNonNull(tokenUri, ERR_MSG_FORMAT.formatted(PREFIX, "token-uri")); - this.userInfoUri = requireNonNull(userInfoUri, ERR_MSG_FORMAT.formatted(PREFIX, "user-info-uri")); - } - /* @formatter:on */ - - public String getClientId() { - return clientId; - } - - public String getClientSecret() { - return clientSecret; - } - - public String getProvider() { - return provider; - } - - public String getRedirectUri() { - return redirectUri; - } - - public String getIssuerUri() { - return issuerUri; - } - - public String getAuthorizationUri() { - return authorizationUri; - } - - public String getTokenUri() { - return tokenUri; - } - - public String getUserInfoUri() { - return userInfoUri; - } - -} \ No newline at end of file diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2PropertiesConfiguration.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2PropertiesConfiguration.java deleted file mode 100644 index 6a38238df3..0000000000 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2PropertiesConfiguration.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.spring.security; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties(LoginOAuth2Properties.class) -@ConditionalOnProperty(prefix = AbstractSecurityConfiguration.LOGIN_PROPERTIES_PREFIX, name = "enabled", havingValue = "true") -class LoginOAuth2PropertiesConfiguration { - -} diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2SuccessHandler.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2SuccessHandler.java index 2cd522aa57..949d58f8db 100644 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2SuccessHandler.java +++ b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2SuccessHandler.java @@ -35,8 +35,6 @@ * and encoded using {@link Base64}. *

* - * @see LoginSecurityConfiguration - * @see LoginOAuth2Properties * @see OAuth2AuthorizedClientService * * @author hamidonos @@ -48,17 +46,17 @@ class LoginOAuth2SuccessHandler implements AuthenticationSuccessHandler { private static final int DEFAULT_EXPIRY_SECONDS = 3600; private static final String BASE_PATH = "/"; - private final LoginOAuth2Properties loginOAuth2Properties; + private final String provider; private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService; private final AES256Encryption aes256Encryption; private final String redirectUri; /* @formatter:off */ - public LoginOAuth2SuccessHandler(LoginOAuth2Properties loginOAuth2Properties, + public LoginOAuth2SuccessHandler(String provider, OAuth2AuthorizedClientService oAuth2AuthorizedClientService, AES256Encryption aes256Encryption, String redirectUri) { - this.loginOAuth2Properties = requireNonNull(loginOAuth2Properties, "Property loginOAuth2Properties must not be null"); + this.provider = requireNonNull(provider, "Property provider must not be null"); this.oAuth2AuthorizedClientService = requireNonNull(oAuth2AuthorizedClientService, "Property oAuth2AuthorizedClientService must not be null"); this.aes256Encryption = requireNonNull(aes256Encryption, "Property aes256Encryption must not be null"); this.redirectUri = requireNonNull(redirectUri, "Property redirectUri must not be null"); @@ -81,8 +79,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo } private OAuth2AccessToken getAccessTokenFromAuthentication(Authentication authentication) { - OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient(loginOAuth2Properties.getProvider(), - authentication.getName()); + OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient(provider, authentication.getName()); return oAuth2AuthorizedClient.getAccessToken(); } diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginProperties.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginProperties.java deleted file mode 100644 index 8aed494eb4..0000000000 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginProperties.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.mercedesbenz.sechub.spring.security; - -// SPDX-License-Identifier: MIT -import java.util.List; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.bind.ConstructorBinding; - -@ConfigurationProperties(prefix = LoginProperties.PREFIX) -public class LoginProperties { - - static final String PREFIX = "sechub.security.login"; - private static final String CLASSIC = "classic"; - private static final String OAUTH2 = "oauth2"; - - private final boolean isEnabled; - private final String redirectUri; - private final List modes; - - @ConstructorBinding - LoginProperties(boolean enabled, String redirectUri, List modes) { - this.isEnabled = enabled; - this.redirectUri = redirectUri; - this.modes = modes; - } - - public boolean isEnabled() { - return isEnabled; - } - - public String getRedirectUri() { - return redirectUri; - } - - public List getModes() { - return modes; - } - - public boolean isClassicModeEnabled() { - return modes.contains(CLASSIC); - } - - public boolean isOAuth2ModeEnabled() { - return modes.contains(OAUTH2); - } -} diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginPropertiesConfiguration.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginPropertiesConfiguration.java deleted file mode 100644 index de54906e56..0000000000 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/LoginPropertiesConfiguration.java +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.spring.security; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ConditionalOnProperty(prefix = LoginProperties.PREFIX, name = "enabled", havingValue = "true") -@EnableConfigurationProperties(LoginProperties.class) -class LoginPropertiesConfiguration { -} diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/MissingAuthenticationEntryPointHandler.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/MissingAuthenticationEntryPointHandler.java index 2b56317472..c7a093d377 100644 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/MissingAuthenticationEntryPointHandler.java +++ b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/MissingAuthenticationEntryPointHandler.java @@ -13,8 +13,8 @@ * {@code MissingAuthenticationEntryPointHandler} implements * {@link AuthenticationEntryPoint} to provide custom behavior upon missing or * invalid authentication. This class is used by Spring's - * oauth2ResourceServer configuration to redirect the user to the login - * page if the user is not authenticated. + * oauth2ResourceServer configuration to redirect the user to the + * specified location if the user is not authenticated. * * @author hamidonos */ diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtAuthenticationProvider.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtAuthenticationProvider.java index dce0aa5016..c5cc9d86f0 100644 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtAuthenticationProvider.java +++ b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtAuthenticationProvider.java @@ -24,10 +24,9 @@ * *

* The {@link org.springframework.security.oauth2.jwt.JwtDecoder} is employed to - * decode the JWT token, extracting the username by interacting with the - * identity provider. This username is then utilized to retrieve user details - * from the user details service. These details are subsequently used to create - * a + * decode the JWT token, extracting the username by decoding the JWT. This + * username is then utilized to retrieve user details from the user details + * service. These details are subsequently used to create a * {@link org.springframework.security.authentication.UsernamePasswordAuthenticationToken}, * which encapsulates information about the authenticated user and their roles. *

diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtProperties.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtProperties.java deleted file mode 100644 index 7d6cd51d40..0000000000 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtProperties.java +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.spring.security; - -import static java.util.Objects.requireNonNull; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(OAuth2JwtProperties.PREFIX) -public class OAuth2JwtProperties { - - static final String PREFIX = "sechub.security.server.oauth2.jwt"; - private final String jwkSetUri; - - OAuth2JwtProperties(String jwkSetUri) { - this.jwkSetUri = requireNonNull(jwkSetUri, "Property '%s.%s' must not be null".formatted(PREFIX, "jwk-set-uri")); - } - - public String getJwkSetUri() { - return jwkSetUri; - } -} \ No newline at end of file diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtPropertiesConfiguration.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtPropertiesConfiguration.java deleted file mode 100644 index b13144e91a..0000000000 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtPropertiesConfiguration.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.mercedesbenz.sechub.spring.security; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties(OAuth2JwtProperties.class) -@ConditionalOnProperty(prefix = AbstractSecurityConfiguration.SERVER_OAUTH2_PROPERTIES_PREFIX, name = AbstractSecurityConfiguration.MODE, havingValue = OAuth2JwtPropertiesConfiguration.MODE) -class OAuth2JwtPropertiesConfiguration { - static final String MODE = "JWT"; -} diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenIntrospectionResponse.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenIntrospectionResponse.java index 4c283d82ea..792f2c6b1f 100644 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenIntrospectionResponse.java +++ b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenIntrospectionResponse.java @@ -9,10 +9,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * OAuth2OpaqueTokenIntrospectionResponse represents the response from the OAuth2 - * opaque token introspection endpoint. It contains various properties related - * to the token, such as its active status, scope, client ID, client type, - * username, token type, expiration time, subject, audience, and group type. + * OAuth2OpaqueTokenIntrospectionResponse represents the response + * from the OAuth2 opaque token introspection endpoint. It contains various + * properties related to the token, such as its active status, scope, client ID, + * client type, username, token type, expiration time, subject, audience, and + * group type. * *

* The active property is required and indicates whether the token diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenProperties.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenProperties.java deleted file mode 100644 index 6b17e314b2..0000000000 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenProperties.java +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.spring.security; - -import static java.util.Objects.requireNonNull; - -import javax.crypto.SealedObject; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -import com.mercedesbenz.sechub.commons.core.security.CryptoAccess; - -@ConfigurationProperties(OAuth2OpaqueTokenProperties.PREFIX) -public class OAuth2OpaqueTokenProperties { - - static final String PREFIX = "sechub.security.server.oauth2.opaque-token"; - private static final String ERR_MSG_FORMAT = "Property '%s.%s' must not be null"; - private static final CryptoAccess CRYPTO_STRING = CryptoAccess.CRYPTO_STRING; - - private final String introspectionUri; - private final SealedObject clientIdSealed; - private final SealedObject clientSecretSealed; - - /* @formatter:off */ - OAuth2OpaqueTokenProperties(String introspectionUri, - String clientId, - String clientSecret) { - this.introspectionUri = requireNonNull(introspectionUri, ERR_MSG_FORMAT.formatted(PREFIX, "introspection-uri")); - this.clientIdSealed = CRYPTO_STRING.seal(requireNonNull(clientId, ERR_MSG_FORMAT.formatted(PREFIX, "client-id"))); - this.clientSecretSealed = CRYPTO_STRING.seal(requireNonNull(clientSecret, ERR_MSG_FORMAT.formatted(PREFIX, "client-secret"))); - } - /* @formatter:on */ - - public String getIntrospectionUri() { - return introspectionUri; - } - - public String getClientId() { - return CRYPTO_STRING.unseal(clientIdSealed); - } - - public String getClientSecret() { - return CRYPTO_STRING.unseal(clientSecretSealed); - } -} \ No newline at end of file diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenPropertiesConfiguration.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenPropertiesConfiguration.java deleted file mode 100644 index e0180fe8d3..0000000000 --- a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenPropertiesConfiguration.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.spring.security; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties(OAuth2OpaqueTokenProperties.class) -@ConditionalOnProperty(prefix = AbstractSecurityConfiguration.SERVER_OAUTH2_PROPERTIES_PREFIX, name = AbstractSecurityConfiguration.MODE, havingValue = OAuth2OpaqueTokenPropertiesConfiguration.MODE) -class OAuth2OpaqueTokenPropertiesConfiguration { - static final String MODE = "OPAQUE_TOKEN"; -} \ No newline at end of file diff --git a/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/security/PortAccessGuard.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/PortAccessGuard.java similarity index 74% rename from sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/security/PortAccessGuard.java rename to sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/PortAccessGuard.java index d01591513f..cf52e24fff 100644 --- a/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/security/PortAccessGuard.java +++ b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/PortAccessGuard.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.webserver.security; +package com.mercedesbenz.sechub.spring.security; import java.io.IOException; @@ -11,8 +11,14 @@ import jakarta.servlet.http.HttpServletResponse; /** - * Filter which checks if the request is targeting the allowed port. If not, it - * will return a 403 Forbidden response. + * Filter which checks if the request path is targeting the allowed port. If + * not, it will return a 403 Forbidden response. + * + *

+ * For example, if the allowed port is 8080, and the request is + * targeting 8081, the filter will return a + * 403 Forbidden response . + *

* * @author hamidonos */ diff --git a/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/SecurityProperties.java b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/SecurityProperties.java new file mode 100644 index 0000000000..c023f8aa88 --- /dev/null +++ b/sechub-commons-security-spring/src/main/java/com/mercedesbenz/sechub/spring/security/SecurityProperties.java @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.spring.security; + +import static java.util.Objects.requireNonNull; + +import java.util.Set; + +import javax.crypto.SealedObject; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +import com.mercedesbenz.sechub.commons.core.security.CryptoAccess; + +@ConfigurationProperties(prefix = SecurityProperties.PREFIX) +public class SecurityProperties { + static final String PREFIX = "sechub.security"; + + private static final Logger LOG = LoggerFactory.getLogger(SecurityProperties.class); + private static final String ERR_MSG_FORMAT = "The property '%s.%s' must not be null"; + private static final String OAUTH2_MODE = "oauth2"; + private static final String CLASSIC_MODE = "classic"; + private static final Set ALLOWED_MODES = Set.of(OAUTH2_MODE, CLASSIC_MODE); + + /** + * Holds all the configuration properties for the server to authenticate + * incoming requests. Authentication can be handled either in 'oauth2' mode or + * 'classic' mode. Set this to null if none of these modes is needed (e.g. when + * testing) + */ + private final Server server; + + /** + * Configures the server to offer login functionality for users. With this + * configuration, the server will be able to provide authentication to users. + * Set this to null if the server should not offer login functionality. + */ + private final Login login; + + private final Encryption encryption; + + @ConstructorBinding + public SecurityProperties(Server server, Login login, Encryption encryption) { + this.server = server; + if (server == null) { + LOG.warn("The property '%s.server' is not set. The server will not be able to authenticate requests".formatted(PREFIX)); + } + this.login = login; + this.encryption = login != null && login.isEnabled() ? requireNonNull(encryption, ERR_MSG_FORMAT.formatted(PREFIX, "encryption")) : encryption; + } + + public Server getServer() { + return server; + } + + public Login getLogin() { + return login; + } + + public Encryption getEncryption() { + return encryption; + } + + public static class Server { + static final String PREFIX = "%s.server".formatted(SecurityProperties.PREFIX); + + private final Set modes; + private final OAuth2 oAuth2; + + @ConstructorBinding + public Server(Set modes, OAuth2 oAuth2) { + this.modes = requireNonNull(modes, ERR_MSG_FORMAT.formatted(PREFIX, "modes")); + if (this.modes.isEmpty()) { + throw new IllegalArgumentException("The property '%s.modes' must at least include 'oauth2' or 'classic' mode".formatted(PREFIX)); + } + if (this.modes.stream().noneMatch(ALLOWED_MODES::contains)) { + throw new IllegalArgumentException("The property '%s.modes' allows only 'oauth2' or 'classic' mode".formatted(PREFIX)); + } + this.oAuth2 = oAuth2; + } + + public Set getModes() { + return modes; + } + + public boolean isOAuth2ModeEnabled() { + return modes.contains(OAUTH2_MODE); + } + + public boolean isClassicModeEnabled() { + return modes.contains(CLASSIC_MODE); + } + + public OAuth2 getOAuth2() { + return oAuth2; + } + + public static class OAuth2 { + static final String PREFIX = "%s.oauth2".formatted(Server.PREFIX); + public static final String OAUTH2_JWT_MODE = "jwt"; + public static final String OAUTH2_OPAQUE_TOKEN_MODE = "opaque-token"; + private static final Set ALLOWED_MODES = Set.of(OAUTH2_JWT_MODE, OAUTH2_OPAQUE_TOKEN_MODE); + + private final String mode; + private final Jwt jwt; + private final OpaqueToken opaqueToken; + + @ConstructorBinding + public OAuth2(String mode, Jwt jwt, OpaqueToken opaqueToken) { + this.mode = requireNonNull(mode, ERR_MSG_FORMAT.formatted(PREFIX, "mode")); + if (!ALLOWED_MODES.contains(mode)) { + throw new IllegalArgumentException("The property '%s.mode' allows only 'jwt' or 'opaque-token' mode".formatted(PREFIX)); + } + this.jwt = OAUTH2_JWT_MODE.equals(this.mode) ? requireNonNull(jwt, ERR_MSG_FORMAT.formatted(PREFIX, "jwt")) : null; + this.opaqueToken = OAUTH2_OPAQUE_TOKEN_MODE.equals(this.mode) ? requireNonNull(opaqueToken, ERR_MSG_FORMAT.formatted(PREFIX, "opaque-token")) + : null; + } + + public String getMode() { + return mode; + } + + public boolean isJwtModeEnabled() { + return OAUTH2_JWT_MODE.equals(mode); + } + + public boolean isOpaqueTokenModeEnabled() { + return OAUTH2_OPAQUE_TOKEN_MODE.equals(mode); + } + + public Jwt getJwt() { + return jwt; + } + + public OpaqueToken getOpaqueToken() { + return opaqueToken; + } + + public static class Jwt { + static final String PREFIX = "%s.jwt".formatted(OAuth2.PREFIX); + + private final String jwkSetUri; + + @ConstructorBinding + public Jwt(String jwkSetUri) { + this.jwkSetUri = requireNonNull(jwkSetUri, ERR_MSG_FORMAT.formatted(PREFIX, "jwk-set-uri")); + } + + public String getJwkSetUri() { + return jwkSetUri; + } + } + + public static class OpaqueToken { + static final String PREFIX = "%s.opaque-token".formatted(OAuth2.PREFIX); + + private final String introspectionUri; + private final String clientId; + private final String clientSecret; + + @ConstructorBinding + public OpaqueToken(String introspectionUri, String clientId, String clientSecret) { + this.introspectionUri = requireNonNull(introspectionUri, ERR_MSG_FORMAT.formatted(PREFIX, "introspection-uri")); + this.clientId = requireNonNull(clientId, ERR_MSG_FORMAT.formatted(PREFIX, "client-id")); + this.clientSecret = requireNonNull(clientSecret, ERR_MSG_FORMAT.formatted(PREFIX, "client-secret")); + } + + public String getIntrospectionUri() { + return introspectionUri; + } + + public String getClientId() { + return clientId; + } + + public String getClientSecret() { + return clientSecret; + } + } + } + } + + public static class Login { + static final String PREFIX = "%s.login".formatted(SecurityProperties.PREFIX); + + private final boolean isEnabled; + private final String loginPage; + private final String redirectUri; + private final Set modes; + private final OAuth2 oAuth2; + + public Login(Boolean enabled, String loginPage, String redirectUri, Set modes, OAuth2 oAuth2) { + this.isEnabled = requireNonNull(enabled, ERR_MSG_FORMAT.formatted(PREFIX, "enabled")); + this.loginPage = requireNonNull(loginPage, ERR_MSG_FORMAT.formatted(PREFIX, "login-page")); + this.redirectUri = requireNonNull(redirectUri, ERR_MSG_FORMAT.formatted(PREFIX, "redirect-uri")); + this.modes = requireNonNull(modes, ERR_MSG_FORMAT.formatted(PREFIX, "modes")); + if (this.modes.isEmpty()) { + throw new IllegalArgumentException("The property '%s.modes' must at least include 'oauth2' or 'classic' mode".formatted(PREFIX)); + } + if (this.modes.stream().noneMatch(ALLOWED_MODES::contains)) { + throw new IllegalArgumentException("The property '%s.modes' allows only 'oauth2' or 'classic' mode".formatted(PREFIX)); + } + /* + * Later we will differentiate between classic and oauth2 login. For now only + * oauth2 login is enabled + */ + this.oAuth2 = requireNonNull(oAuth2, ERR_MSG_FORMAT.formatted(PREFIX, "oauth2")); + } + + public boolean isEnabled() { + return isEnabled; + } + + public String getLoginPage() { + return loginPage; + } + + public String getRedirectUri() { + return redirectUri; + } + + public Set getModes() { + return modes; + } + + public boolean isOAuth2ModeEnabled() { + return modes.contains(OAUTH2_MODE); + } + + public boolean isClassicModeEnabled() { + return modes.contains(CLASSIC_MODE); + } + + public OAuth2 getOAuth2() { + return oAuth2; + } + + public static class OAuth2 { + static final String PREFIX = "%s.oauth2".formatted(Login.PREFIX); + + private final String clientId; + private final String clientSecret; + private final String provider; + private final String redirectUri; + private final String issuerUri; + private final String authorizationUri; + private final String tokenUri; + private final String userInfoUri; + private final String jwkSetUri; + + @ConstructorBinding + public OAuth2(String clientId, String clientSecret, String provider, String redirectUri, String issuerUri, String authorizationUri, String tokenUri, + String userInfoUri, String jwkSetUri) { + this.clientId = requireNonNull(clientId, ERR_MSG_FORMAT.formatted(PREFIX, "client-id")); + this.clientSecret = requireNonNull(clientSecret, ERR_MSG_FORMAT.formatted(PREFIX, "client-secret")); + this.provider = requireNonNull(provider, ERR_MSG_FORMAT.formatted(PREFIX, "provider")); + this.redirectUri = requireNonNull(redirectUri, ERR_MSG_FORMAT.formatted(PREFIX, "redirect-uri")); + this.issuerUri = requireNonNull(issuerUri, ERR_MSG_FORMAT.formatted(PREFIX, "issuer-uri")); + this.authorizationUri = requireNonNull(authorizationUri, ERR_MSG_FORMAT.formatted(PREFIX, "authorization-uri")); + this.tokenUri = requireNonNull(tokenUri, ERR_MSG_FORMAT.formatted(PREFIX, "token-uri")); + this.userInfoUri = requireNonNull(userInfoUri, ERR_MSG_FORMAT.formatted(PREFIX, "user-info-uri")); + this.jwkSetUri = requireNonNull(jwkSetUri, ERR_MSG_FORMAT.formatted(PREFIX, "jwk-set-uri")); + } + + public String getClientId() { + return clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public String getProvider() { + return provider; + } + + public String getRedirectUri() { + return redirectUri; + } + + public String getIssuerUri() { + return issuerUri; + } + + public String getAuthorizationUri() { + return authorizationUri; + } + + public String getTokenUri() { + return tokenUri; + } + + public String getUserInfoUri() { + return userInfoUri; + } + + public String getJwkSetUri() { + return jwkSetUri; + } + + } + } + + public static class Encryption { + static final String PREFIX = "%s.encryption".formatted(SecurityProperties.PREFIX); + private static final int AES_256_SECRET_KEY_LENGTH = 32; + + private final SealedObject secretKey; + + @ConstructorBinding + public Encryption(String secretKey) { + requireNonNull(secretKey, ERR_MSG_FORMAT.formatted(PREFIX, "secret-key")); + if (!is256BitString(secretKey)) { + throw new IllegalArgumentException("The property %s.%s must be a 256-bit string".formatted(PREFIX, "secret-key")); + } + this.secretKey = CryptoAccess.CRYPTO_STRING.seal(secretKey); + } + + public String getSecretKey() { + return CryptoAccess.CRYPTO_STRING.unseal(secretKey); + } + + /* + * Checks if the secret key length is 32 characters (32 * 8 = 256 bits) + */ + private static boolean is256BitString(String secretKey) { + return secretKey.length() == AES_256_SECRET_KEY_LENGTH; + } + } + /* @formatter:on */ +} diff --git a/sechub-commons-security-spring/src/main/resources/static/favicon.ico b/sechub-commons-security-spring/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..02fcae3958a8a5ad8d07e2faa2eae44b6cef275a GIT binary patch literal 4286 zcmcIn4LFr)8a^(ywZ*m5b@j752o;^f;T%+?{N`^Eg~phg$!uxoYOZZe#iS8R%Ct)j zZ8H6g-zE~uio~qSU#M0T5;2;72ID8h#CPxWey1a@n5LawuKT>-dEW1N@B4k8=Y78K z2m}iBuc0AepMukG2?VnR0)db~CV?e^*(dq~AQ%rSYHE&FVU~@Obx|e>0e#roI;)*%LPuIfulO?Shl6k(WbLam4x9(l;Z@S&| zw)IKgL4p#Tz>Z3li3=HGKlR<>nbYWT906#BIs)W&IYfi^mcYypi zy(u@l-#pm-it`C4EZu6t>3G>gC##uX+q<+I*W3DeUdGLEzFmgo2ZL}e?&j~)J5>qG z5OeZhiusEc?4o>3dlMa0<2Tlv^MHM%4a+URWIcoPMna6JL``x0F>d+8NBTJlmG^;&w;xn0=QuwIDDh0@1}) zBQana4%hy~>y;L?Q_Wa{#nC$48d7fqkcJ4>gI=d&@#e+x?0o5*mgPLXv1csz{T95J zycQnk-Qo4M?-+ij!KJ8^*C5^<15-a!&VkO2M}|9`Pdaf1e>S{EI@EtjgZ^s$m}UM; z`LktEI*7WiXCwTVYHnd&`g&~1^2fe{Xsk?hM#7zkV>EnmLly(!kpk$RDB*-Bia0G7 zN6zN&E8vmo&YA5pYo160s}-XShF6z_1oE-pSRt!(zSjR;3D?su2+NNmO;2&^)(t)e zq1hM5@JBSrYCx278`|M%kkB6V|J4aipJ2}5BSW~Qt%h`+v_KvMU3YO7_4x{$NSg|a z-4^|}kv5zp{3sv8;PUp7y?>b-gRs0PH1-biw(Lj!c=zlFc>n8dWB7O9Y?sx*ncI-& zcR=UZRY+OSpdPVcwI@gG!|tFhs|S0gK~+s{x{;63R?9sWmCO81%gut!&TBJ1d|jM|F|H&oL}Qy^UL_ldD&RsH^lEbwx&*&gPfN+_^4Etf66_ir9VS2 z=LcQ`i-9Pvj5GV(3{J6*obji|v2tGdIA`{3!8B!MbDfNPJbz(p_qhA7>6P(kJRX4O zS#Nk9|7;`&R)rnBuhza4NUse+H=_aCQMvs7=kGj<<$lXK*CZEC%St;y&MP04rcYNO z{!Q%sviM(r*fB2u&8IJWPfT?U4wY8${OJ#$@qUK%ToN9=%t@~eLnrAvB$s>m8Wh@# z=ox0rIUKgP1 z>O^JdFfyCkM>rXGa#IvF6#b=m$!Eao8zoQUd3NcFd9?;fl;V2zdW&d~SNPo#4` zWi;>TYfn#4!Q@Gk6sJy`YM~i@F;AG@FrtHLc^wvg*MWuWH({CoGFXOMg8EIHS%_=f z(!@+GJWm--v3Jt;~REv5PQ{sQ)=dw^J6p)|$ z+Mvi=CvP+%q2>X;ZTyKx!ktIh5EhA(w_0$nZ3r2U2l?D2HTA%5<7e6Ar$TTOXFs4` z_?+?Ym}t)v*~v#5l$wuZ>h)(@x_KUX%^k=q!+U{Y*qM|urY6`t)AH`FgSq5COBj2Z z&)qyPzqpBbCbFxjzP&TH{2tO;dXUrnBetA9gk8DOxYO|zS@aH+_I(d>pA4Y)r@6Aqj05S0^!ipD0Mf6LL++{`(11ap@KO_xLDW5^Ut&P zN@Q!&8X~9UO88%E|F3Fr!~U3-bkgcE|C0mfXdNYjS#)VVqDyL-F0)_3JyU!4x6R}7vR+h1{f_+xU>ZvM==$eZ z_0fIocgp;2hl|+zjlIwI)Rgag;T;}(i@yKjZxsy}pLqRZSx@EnRn!mhKU<-f-UKl{ zSM<^vAkJ)tnEH@7qZxXswa`r@JDX?co$5^<5BF%2GDgU;{Et-x|`zgM2Y z@|aM3l(i$W2ZYB<`UpK*!AX^5+ek1X7!rg8>5K2o#-K;glG~{7`D*V?X new AES256EncryptionProperties(null)) - .isInstanceOf(NullPointerException.class) - .hasMessageContaining("The property 'sechub.security.encryption.secret-key' must not be null"); - /* @formatter:on */ - } - - @ParameterizedTest - @ValueSource(strings = { "", "1", "est-test-test-test-test-test-31", "-test-test-test-test-test-test-33" }) - void construct_aes256encryption_properties_with_non_256_bit_long_secret_key_fails(String secretKey) { - /* @formatter:off */ - /* execute & test */ - assertThatThrownBy(() -> new AES256EncryptionProperties(secretKey)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("The property sechub.security.encryption.secret-key must be a 256-bit string"); - /* @formatter:on */ - } - - @Configuration - @EnableConfigurationProperties(AES256EncryptionProperties.class) - static class TestConfig { - } -} diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/AES256EncryptionTest.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/AES256EncryptionTest.java index 219725efdd..bb637ae0d5 100644 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/AES256EncryptionTest.java +++ b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/AES256EncryptionTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; import java.security.GeneralSecurityException; import java.util.Base64; @@ -13,13 +14,14 @@ class AES256EncryptionTest { private static final String VALID_AES_256_TEST_SECRET_KEY = "test-test-test-test-test-test-32"; - private static final AES256EncryptionProperties aes256EncryptionProperties = new AES256EncryptionProperties(VALID_AES_256_TEST_SECRET_KEY); + private static final SecurityProperties securityProperties = new SecurityProperties(mock(), mock(), + new SecurityProperties.Encryption(VALID_AES_256_TEST_SECRET_KEY)); private static final AES256Encryption aes256Encryption; private static final Base64.Encoder ENCODER = Base64.getEncoder(); static { try { - aes256Encryption = new AES256Encryption(aes256EncryptionProperties); + aes256Encryption = new AES256Encryption(securityProperties); } catch (GeneralSecurityException e) { throw new TestAbortedException("Failed to prepare AES256EncryptionTest", e); } diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/CookieAccessTokenResolverTest.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/DynamicBearerTokenResolverTest.java similarity index 77% rename from sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/CookieAccessTokenResolverTest.java rename to sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/DynamicBearerTokenResolverTest.java index 4ea90e6b8b..b6f019d233 100644 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/CookieAccessTokenResolverTest.java +++ b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/DynamicBearerTokenResolverTest.java @@ -4,6 +4,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -18,12 +20,11 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; -import org.mockito.Mockito; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; -class CookieAccessTokenResolverTest { +class DynamicBearerTokenResolverTest { private static final String MISSING_ACCESS_TOKEN = "missing-access-token"; private static final String ACCESS_TOKEN_KEY = "access_token"; @@ -34,25 +35,40 @@ class CookieAccessTokenResolverTest { private static final String BASE_PATH = "/"; private static final AES256Encryption aes256Encryption = mock(); - private static final CookieAccessTokenResolver cookieAccessTokenResolver = new CookieAccessTokenResolver(aes256Encryption); + private static final DynamicBearerTokenResolver DYNAMIC_BEARER_TOKEN_RESOLVER = new DynamicBearerTokenResolver(aes256Encryption); private static final HttpServletRequest httpServletRequest = mock(); @BeforeEach void beforeEach() { - Mockito.reset(aes256Encryption); + reset(aes256Encryption, httpServletRequest); when(aes256Encryption.decrypt(any())).thenReturn(ACCESS_TOKEN); } @Test - void resolve_reads_and_decrypts_access_token_from_cookies_successfully() { + void resolve_reads_access_token_from_authorization_header_successfully() { /* prepare */ + when(httpServletRequest.getHeader("Authorization")).thenReturn("Bearer " + ACCESS_TOKEN); + + /* execute */ + String accessToken = DYNAMIC_BEARER_TOKEN_RESOLVER.resolve(httpServletRequest); + + /* test */ + assertThat(accessToken).isEqualTo(ACCESS_TOKEN); + verify(httpServletRequest, never()).getCookies(); + verify(aes256Encryption, never()).decrypt(DECRYPTED_ACCESS_TOKEN_B64_DECODED); + } + + @Test + void resolve_reads_and_decrypts_access_token_from_cookies_successfully_when_no_authorization_header_is_available() { + /* prepare */ + when(httpServletRequest.getHeader("Authorization")).thenReturn(""); Cookie cookie = createAccessTokenCookie(ACCESS_TOKEN_KEY, ENCRYPTED_ACCESS_TOKEN_B64_ENCODED); Cookie someOtherCookie = createAccessTokenCookie("some-other-cookie-name", "some-other-cookie-value"); Cookie[] cookies = List.of(cookie, someOtherCookie).toArray(new Cookie[0]); when(httpServletRequest.getCookies()).thenReturn(cookies); /* execute */ - String accessToken = cookieAccessTokenResolver.resolve(httpServletRequest); + String accessToken = DYNAMIC_BEARER_TOKEN_RESOLVER.resolve(httpServletRequest); /* test */ assertThat(accessToken).isEqualTo(ACCESS_TOKEN); @@ -60,14 +76,14 @@ void resolve_reads_and_decrypts_access_token_from_cookies_successfully() { } @ParameterizedTest - @ArgumentsSource(CookieAccessTokenResolverTest.InvalidCookieListProvider.class) + @ArgumentsSource(DynamicBearerTokenResolverTest.InvalidCookieListProvider.class) void resolve_returns_missing_access_token_value_when_access_token_cookie_is_not_found(List cookies) { /* prepare */ Cookie[] array = cookies == null ? null : cookies.toArray(new Cookie[0]); when(httpServletRequest.getCookies()).thenReturn(array); /* execute */ - String accessToken = cookieAccessTokenResolver.resolve(httpServletRequest); + String accessToken = DYNAMIC_BEARER_TOKEN_RESOLVER.resolve(httpServletRequest); /* test */ assertThat(accessToken).isEqualTo(MISSING_ACCESS_TOKEN); @@ -82,7 +98,7 @@ void resolve_returns_missing_access_token_value_when_access_token_decoding_fails /* execute & test */ - String accessToken = cookieAccessTokenResolver.resolve(httpServletRequest); + String accessToken = DYNAMIC_BEARER_TOKEN_RESOLVER.resolve(httpServletRequest); /* test */ assertThat(accessToken).isEqualTo(MISSING_ACCESS_TOKEN); @@ -98,7 +114,7 @@ void resolve_returns_missing_access_token_value_when_access_token_decryption_fai /* execute & test */ - String accessToken = cookieAccessTokenResolver.resolve(httpServletRequest); + String accessToken = DYNAMIC_BEARER_TOKEN_RESOLVER.resolve(httpServletRequest); /* test */ assertThat(accessToken).isEqualTo(MISSING_ACCESS_TOKEN); diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2PropertiesTest.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2PropertiesTest.java deleted file mode 100644 index 6e96f67a08..0000000000 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2PropertiesTest.java +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.spring.security; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.stream.Stream; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.TestPropertySource; - -import com.mercedesbenz.sechub.testframework.spring.YamlPropertyLoaderFactory; - -@SpringBootTest -@TestPropertySource(locations = "classpath:application-login-oauth2-test.yaml", factory = YamlPropertyLoaderFactory.class) -class LoginOAuth2PropertiesTest { - - private static final String ERR_MSG_FORMAT = "The property 'sechub.security.login.oauth2.%s' must not be null"; - - private final LoginOAuth2Properties properties; - - @Autowired - LoginOAuth2PropertiesTest(LoginOAuth2Properties properties) { - this.properties = properties; - } - - @Test - void construct_login_o_auth_2_properties_with_valid_properties_file_succeeds() { - assertThat(properties.getClientId()).isEqualTo("client-id"); - assertThat(properties.getClientSecret()).isEqualTo("client-secret"); - assertThat(properties.getProvider()).isEqualTo("provider"); - assertThat(properties.getRedirectUri()).isEqualTo("redirect-uri"); - assertThat(properties.getIssuerUri()).isEqualTo("issuer-uri"); - assertThat(properties.getAuthorizationUri()).isEqualTo("authorization-uri"); - assertThat(properties.getTokenUri()).isEqualTo("token-uri"); - assertThat(properties.getUserInfoUri()).isEqualTo("user-info-uri"); - } - - /* @formatter:off */ - @ParameterizedTest - @ArgumentsSource(InvalidLoginOAuth2PropertiesProvider.class) - void construct_login_o_auth_2_properties_with_null_property_fails(String clientId, - String clientSecret, - String provider, - String redirectUri, - String issuerUri, - String authorizationUri, - String tokenUri, - String userInfoUri, - String errMsg) { - Assertions.assertThatThrownBy(() -> new LoginOAuth2Properties(clientId, clientSecret, provider, redirectUri, issuerUri, authorizationUri, tokenUri, userInfoUri)) - .isInstanceOf(NullPointerException.class) - .hasMessageContaining(errMsg); - } - /* @formatter:on */ - - @Configuration - @Import(LoginOAuth2PropertiesConfiguration.class) - static class TestConfig { - } - - private static class InvalidLoginOAuth2PropertiesProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(ExtensionContext extensionContext) throws Exception { - /* @formatter:off */ - return Stream.of( - Arguments.of(null, "client-secret", "provider", "redirect-uri", "issuer-uri", "authorization-uri", "token-uri", "user-info-uri", String.format(ERR_MSG_FORMAT, "client-id")), - Arguments.of("client-id", null, "provider", "redirect-uri", "issuer-uri", "authorization-uri", "token-uri", "user-info-uri", String.format(ERR_MSG_FORMAT, "client-secret")), - Arguments.of("client-id", "client-secret", null, "redirect-uri", "issuer-uri", "authorization-uri", "token-uri", "user-info-uri", String.format(ERR_MSG_FORMAT, "provider")), - Arguments.of("client-id", "client-secret", "provider", null, "issuer-uri", "authorization-uri", "token-uri", "user-info-uri", String.format(ERR_MSG_FORMAT, "redirect-uri")), - Arguments.of("client-id", "client-secret", "provider", "redirect-uri", null, "authorization-uri", "token-uri", "user-info-uri", String.format(ERR_MSG_FORMAT, "issuer-uri")), - Arguments.of("client-id", "client-secret", "provider", "redirect-uri", "issuer-uri", null, "token-uri", "user-info-uri", String.format(ERR_MSG_FORMAT, "authorization-uri")), - Arguments.of("client-id", "client-secret", "provider", "redirect-uri", "issuer-uri", "authorization-uri", null, "user-info-uri", String.format(ERR_MSG_FORMAT, "token-uri")), - Arguments.of("client-id", "client-secret", "provider", "redirect-uri", "issuer-uri", "authorization-uri", "token-uri", null, String.format(ERR_MSG_FORMAT, "user-info-uri")) - ); - /* @formatter:on */ - } - } -} diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2SuccessHandlerTest.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2SuccessHandlerTest.java index 49a1552534..6087f4b2d2 100644 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2SuccessHandlerTest.java +++ b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/LoginOAuth2SuccessHandlerTest.java @@ -1,6 +1,11 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.spring.security; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.Instant; @@ -10,7 +15,6 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatcher; import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; @@ -33,42 +37,40 @@ class LoginOAuth2SuccessHandlerTest { private static final String BASE_PATH = "/"; private static final String REDIRECT_URI = "https://example.org/redirect-uri"; - private static final LoginOAuth2Properties loginOAuth2Properties = Mockito.mock(); - private static final OAuth2AuthorizedClientService oAuth2AuthorizedClientService = Mockito.mock(); - private static final AES256Encryption aes256Encryption = Mockito.mock(); - private static final HttpServletRequest httpServletRequest = Mockito.mock(); - private static final HttpServletResponse httpServletResponse = Mockito.mock(); - private static final Authentication authentication = Mockito.mock(); - private static final OAuth2AuthorizedClient oauth2AuthorizedClient = Mockito.mock(); - private static final OAuth2AccessToken oAuth2AccessToken = Mockito.mock(); - private static final LoginOAuth2SuccessHandler loginOAuth2SuccessHandler = new LoginOAuth2SuccessHandler(loginOAuth2Properties, - oAuth2AuthorizedClientService, aes256Encryption, REDIRECT_URI); + private static final OAuth2AuthorizedClientService oAuth2AuthorizedClientService = mock(); + private static final AES256Encryption aes256Encryption = mock(); + private static final HttpServletRequest httpServletRequest = mock(); + private static final HttpServletResponse httpServletResponse = mock(); + private static final Authentication authentication = mock(); + private static final OAuth2AuthorizedClient oauth2AuthorizedClient = mock(); + private static final OAuth2AccessToken oAuth2AccessToken = mock(); + private static final LoginOAuth2SuccessHandler loginOAuth2SuccessHandler = new LoginOAuth2SuccessHandler(PROVIDER, oAuth2AuthorizedClientService, + aes256Encryption, REDIRECT_URI); @BeforeEach void beforeEach() { - Mockito.reset(aes256Encryption, httpServletResponse); - Mockito.when(loginOAuth2Properties.getProvider()).thenReturn(PROVIDER); - Mockito.when(aes256Encryption.encrypt(ArgumentMatchers.anyString())).thenReturn(ENCRYPTED_ACCESS_TOKEN_BYTES); - Mockito.when(oAuth2AuthorizedClientService.loadAuthorizedClient(PROVIDER, PRINCIPAL)).thenReturn(oauth2AuthorizedClient); - Mockito.when(authentication.getName()).thenReturn(PRINCIPAL); - Mockito.when(oauth2AuthorizedClient.getAccessToken()).thenReturn(oAuth2AccessToken); - Mockito.when(oAuth2AccessToken.getTokenValue()).thenReturn(ACCESS_TOKEN); + reset(aes256Encryption, httpServletResponse); + when(aes256Encryption.encrypt(ArgumentMatchers.anyString())).thenReturn(ENCRYPTED_ACCESS_TOKEN_BYTES); + when(oAuth2AuthorizedClientService.loadAuthorizedClient(PROVIDER, PRINCIPAL)).thenReturn(oauth2AuthorizedClient); + when(authentication.getName()).thenReturn(PRINCIPAL); + when(oauth2AuthorizedClient.getAccessToken()).thenReturn(oAuth2AccessToken); + when(oAuth2AccessToken.getTokenValue()).thenReturn(ACCESS_TOKEN); } @Test void on_authentication_success_sends_a_valid_redirect_containing_the_encrypted_access_token_cookie() throws IOException { /* prepare */ Instant now = Instant.now(); - Mockito.when(oAuth2AccessToken.getIssuedAt()).thenReturn(now); - Mockito.when(oAuth2AccessToken.getExpiresAt()).thenReturn(now.plusSeconds(60)); + when(oAuth2AccessToken.getIssuedAt()).thenReturn(now); + when(oAuth2AccessToken.getExpiresAt()).thenReturn(now.plusSeconds(60)); int expirySeconds = 60; /* execute */ loginOAuth2SuccessHandler.onAuthenticationSuccess(httpServletRequest, httpServletResponse, authentication); /* test */ - Mockito.verify(aes256Encryption).encrypt(ACCESS_TOKEN); - Mockito.verify(httpServletResponse).sendRedirect(REDIRECT_URI); + verify(aes256Encryption).encrypt(ACCESS_TOKEN); + verify(httpServletResponse).sendRedirect(REDIRECT_URI); ArgumentMatcher argumentMatcher = cookie -> { /* @formatter:off */ if (!ACCESS_TOKEN_KEY.equals(cookie.getName())) return false; @@ -79,21 +81,21 @@ void on_authentication_success_sends_a_valid_redirect_containing_the_encrypted_a return BASE_PATH.equals(cookie.getPath()); /* @formatter:on */ }; - Mockito.verify(httpServletResponse).addCookie(ArgumentMatchers.argThat(argumentMatcher)); + verify(httpServletResponse).addCookie(ArgumentMatchers.argThat(argumentMatcher)); } @Test void on_authentication_success_assumes_default_expiry_when_expires_at_is_null() throws IOException { /* prepare */ Instant now = Instant.now(); - Mockito.when(oAuth2AccessToken.getIssuedAt()).thenReturn(now); - Mockito.when(oAuth2AccessToken.getExpiresAt()).thenReturn(null); + when(oAuth2AccessToken.getIssuedAt()).thenReturn(now); + when(oAuth2AccessToken.getExpiresAt()).thenReturn(null); /* execute */ loginOAuth2SuccessHandler.onAuthenticationSuccess(httpServletRequest, httpServletResponse, authentication); /* test */ ArgumentMatcher argumentMatcher = cookie -> cookie.getMaxAge() == DEFAULT_EXPIRY_SECONDS; - Mockito.verify(httpServletResponse).addCookie(ArgumentMatchers.argThat(argumentMatcher)); + verify(httpServletResponse).addCookie(ArgumentMatchers.argThat(argumentMatcher)); } } \ No newline at end of file diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtIntegrationTest.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtIntegrationTest.java index 1635c03529..203511d7f0 100644 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtIntegrationTest.java +++ b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtIntegrationTest.java @@ -12,7 +12,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -54,7 +53,6 @@ */ @WebMvcTest @TestPropertySource(locations = "classpath:application-jwt-test.yml", factory = YamlPropertyLoaderFactory.class) -@ActiveProfiles("oauth2") class OAuth2JwtIntegrationTest { /** @@ -133,7 +131,7 @@ void api_user_is_accessible_as_user() throws Exception { } @Configuration - @Import({ TestSecurityConfiguration.class, TestOAuth2JwtSecurityConfiguration.class, OAuth2JwtPropertiesConfiguration.class }) + @Import({ TestSecurityConfiguration.class, TestOAuth2JwtSecurityConfiguration.class, AES256Encryption.class }) static class TestConfig { @Bean @@ -141,4 +139,5 @@ TestSecurityController testSecurityController() { return new TestSecurityController(); } } + } diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtPropertiesTest.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtPropertiesTest.java deleted file mode 100644 index e89905dc2b..0000000000 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2JwtPropertiesTest.java +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.spring.security; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; - -import com.mercedesbenz.sechub.testframework.spring.YamlPropertyLoaderFactory; - -@SpringBootTest -@ActiveProfiles("oauth2") -@TestPropertySource(locations = "classpath:application-jwt-test.yml", factory = YamlPropertyLoaderFactory.class) -class OAuth2JwtPropertiesTest { - - private final OAuth2JwtProperties properties; - - OAuth2JwtPropertiesTest(@Autowired OAuth2JwtProperties properties) { - this.properties = properties; - } - - @Test - void construct_properties_with_jwt_enabled_succeeds() { - assertThat(properties.getJwkSetUri()).isEqualTo("https://example.org/jwk-set-uri"); - } - - /* @formatter:off */ - @Test - void construct_properties_with_jwk_set_uri_null_fails() { - assertThatThrownBy(() -> new OAuth2JwtProperties(null)) - .isInstanceOf(NullPointerException.class) - .hasMessageContaining("Property 'sechub.security.server.oauth2.jwt.jwk-set-uri' must not be null"); - } - /* @formatter:on */ - - @Configuration - @Import(OAuth2JwtPropertiesConfiguration.class) - static class TestConfig { - } -} \ No newline at end of file diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenIntegrationTest.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenIntegrationTest.java index 56cd493985..a118ad6c0b 100644 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenIntegrationTest.java +++ b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenIntegrationTest.java @@ -12,7 +12,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -56,7 +55,6 @@ @SuppressWarnings("JavadocReference") @WebMvcTest @TestPropertySource(locations = "classpath:application-opaque-token-test.yml", factory = YamlPropertyLoaderFactory.class) -@ActiveProfiles("oauth2") class OAuth2OpaqueTokenIntegrationTest { /** @@ -135,7 +133,7 @@ void api_user_is_accessible_as_user() throws Exception { } @Configuration - @Import({ TestSecurityConfiguration.class, TestOAuth2OpaqueTokenSecurityConfiguration.class, OAuth2OpaqueTokenPropertiesConfiguration.class }) + @Import({ TestSecurityConfiguration.class, TestOAuth2OpaqueTokenSecurityConfiguration.class, AES256Encryption.class }) static class TestConfig { @Bean diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenIntrospectorTest.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenIntrospectorTest.java index dccfe0fee9..926346af00 100644 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenIntrospectorTest.java +++ b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenIntrospectorTest.java @@ -93,7 +93,8 @@ void introspect_with_null_response_fails() { void introspect_with_inactive_token_fails() { /* prepare */ OAuth2OpaqueTokenIntrospectionResponse OAuth2OpaqueTokenIntrospectionResponse = createOpaqueTokenResponse(Boolean.FALSE, null); - when(restTemplate.postForObject(eq(INTROSPECTION_URI), any(), eq(OAuth2OpaqueTokenIntrospectionResponse.class))).thenReturn(OAuth2OpaqueTokenIntrospectionResponse); + when(restTemplate.postForObject(eq(INTROSPECTION_URI), any(), eq(OAuth2OpaqueTokenIntrospectionResponse.class))) + .thenReturn(OAuth2OpaqueTokenIntrospectionResponse); /* execute & assert */ /* @formatter:off */ @@ -108,7 +109,8 @@ void introspect_with_valid_token_succeeds() { /* prepare */ long expiresAt = 3600L; OAuth2OpaqueTokenIntrospectionResponse OAuth2OpaqueTokenIntrospectionResponse = createOpaqueTokenResponse(Boolean.TRUE, expiresAt); - when(restTemplate.postForObject(eq(INTROSPECTION_URI), any(), eq(OAuth2OpaqueTokenIntrospectionResponse.class))).thenReturn(OAuth2OpaqueTokenIntrospectionResponse); + when(restTemplate.postForObject(eq(INTROSPECTION_URI), any(), eq(OAuth2OpaqueTokenIntrospectionResponse.class))) + .thenReturn(OAuth2OpaqueTokenIntrospectionResponse); Collection authorities = Set.of(new SimpleGrantedAuthority(TestRoles.USER)); when(userDetailsService.loadUserByUsername(SUBJECT)).thenReturn(new TestUserDetails(authorities, SUBJECT)); @@ -134,7 +136,8 @@ void introspect_with_null_expires_at_constructs_principal_with_default_expires_a /* prepare */ Instant now = Instant.now(); OAuth2OpaqueTokenIntrospectionResponse OAuth2OpaqueTokenIntrospectionResponse = createOpaqueTokenResponse(Boolean.TRUE, null); - when(restTemplate.postForObject(eq(INTROSPECTION_URI), any(), eq(OAuth2OpaqueTokenIntrospectionResponse.class))).thenReturn(OAuth2OpaqueTokenIntrospectionResponse); + when(restTemplate.postForObject(eq(INTROSPECTION_URI), any(), eq(OAuth2OpaqueTokenIntrospectionResponse.class))) + .thenReturn(OAuth2OpaqueTokenIntrospectionResponse); Collection authorities = Set.of(new SimpleGrantedAuthority(TestRoles.USER)); when(userDetailsService.loadUserByUsername(SUBJECT)).thenReturn(new TestUserDetails(authorities, SUBJECT)); diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenPropertiesTest.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenPropertiesTest.java deleted file mode 100644 index c3d6be2503..0000000000 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/OAuth2OpaqueTokenPropertiesTest.java +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.spring.security; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.util.stream.Stream; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; - -import com.mercedesbenz.sechub.testframework.spring.YamlPropertyLoaderFactory; - -@SpringBootTest -@ActiveProfiles("oauth2") -@TestPropertySource(locations = "classpath:application-opaque-token-test.yml", factory = YamlPropertyLoaderFactory.class) -class OAuth2OpaqueTokenPropertiesTest { - - private final OAuth2OpaqueTokenProperties properties; - - OAuth2OpaqueTokenPropertiesTest(@Autowired OAuth2OpaqueTokenProperties properties) { - this.properties = properties; - } - - @Test - void construct_properties_with_opaque_token_enabled_succeeds() { - assertThat(properties.getIntrospectionUri()).isEqualTo("https://example.org/introspection-uri"); - assertThat(properties.getClientId()).isEqualTo("example-client-id"); - assertThat(properties.getClientSecret()).isEqualTo("example-client-secret"); - } - - /* @formatter:off */ - @ParameterizedTest - @ArgumentsSource(InvalidOAuth2OpaqueTokenPropertiesProvider.class) - void construct_properties_with_null_arguments_fails(String introspectionUri, - String clientId, - String clientSecret, - String errMsg) { - assertThatThrownBy(() -> new OAuth2OpaqueTokenProperties(introspectionUri, clientId, clientSecret)) - .isInstanceOf(NullPointerException.class) - .hasMessageContaining(errMsg); - } - /* @formatter:on */ - - @Configuration - @Import({ OAuth2OpaqueTokenPropertiesConfiguration.class, OAuth2OpaqueTokenPropertiesConfiguration.class }) - static class TestConfig { - } - - private static class InvalidOAuth2OpaqueTokenPropertiesProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(ExtensionContext extensionContext) { - /* @formatter:off */ - return Stream.of( - Arguments.of(null, "example-client-id", "example-client-secret", "Property 'sechub.security.server.oauth2.opaque-token.introspection-uri' must not be null"), - Arguments.of("https://example.org/introspection-uri", null, "example-client-secret", "Property 'sechub.security.server.oauth2.opaque-token.client-id' must not be null"), - Arguments.of("https://example.org/introspection-uri", "example-client-id", null, "Property 'sechub.security.server.oauth2.opaque-token.client-secret' must not be null") - ); - /* @formatter:on */ - } - } -} \ No newline at end of file diff --git a/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/PortAccessGuardTest.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/PortAccessGuardTest.java similarity index 97% rename from sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/PortAccessGuardTest.java rename to sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/PortAccessGuardTest.java index 428d52ce64..c71fd3a24f 100644 --- a/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/PortAccessGuardTest.java +++ b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/PortAccessGuardTest.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.webserver.security; +package com.mercedesbenz.sechub.spring.security; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/SecurityConfigurationTest.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/SecurityConfigurationTest.java index ffd68f2c7b..aa0b1371b1 100644 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/SecurityConfigurationTest.java +++ b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/SecurityConfigurationTest.java @@ -27,7 +27,7 @@ *

* Note: Here we don't test the integration of OAuth2 or Basic Auth. For * that, see {@link OAuth2JwtIntegrationTest} or - * {@link OAuth2OpaqueTokenIntegrationTest}. T his test class is only concerned + * {@link OAuth2OpaqueTokenIntegrationTest}. This test class is only concerned * with verifying if the security rules are correctly applied on an abstract * level. *

diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/SecurityPropertiesTest.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/SecurityPropertiesTest.java new file mode 100644 index 0000000000..1602d9e685 --- /dev/null +++ b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/SecurityPropertiesTest.java @@ -0,0 +1,431 @@ +package com.mercedesbenz.sechub.spring.security; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.TestPropertySource; + +import com.mercedesbenz.sechub.testframework.spring.YamlPropertyLoaderFactory; + +@SpringBootTest +@TestPropertySource(locations = "classpath:application-security-properties-test.yaml", factory = YamlPropertyLoaderFactory.class) +class SecurityPropertiesTest { + + private final SecurityProperties properties; + + @Autowired + SecurityPropertiesTest(SecurityProperties properties) { + this.properties = properties; + } + + @Test + void construct_security_properties_with_valid_properties_file_succeeds() { + SecurityProperties.Server server = properties.getServer(); + assertThat(server).isNotNull(); + assertThat(server.isOAuth2ModeEnabled()).isTrue(); + assertThat(server.isClassicModeEnabled()).isTrue(); + SecurityProperties.Server.OAuth2 oAuth2 = server.getOAuth2(); + assertThat(oAuth2).isNotNull(); + assertThat(oAuth2.getMode()).isEqualTo("jwt"); + assertThat(oAuth2.isJwtModeEnabled()).isTrue(); + assertThat(oAuth2.isOpaqueTokenModeEnabled()).isFalse(); + SecurityProperties.Server.OAuth2.Jwt jwt = oAuth2.getJwt(); + assertThat(jwt).isNotNull(); + assertThat(jwt.getJwkSetUri()).isEqualTo("https://example.org/jwk-set-uri"); + SecurityProperties.Server.OAuth2.OpaqueToken opaqueToken = oAuth2.getOpaqueToken(); + assertThat(opaqueToken).isNull(); + + SecurityProperties.Login login = properties.getLogin(); + assertThat(login).isNotNull(); + assertThat(login.isEnabled()).isTrue(); + assertThat(login.getLoginPage()).isEqualTo("/login"); + assertThat(login.getRedirectUri()).isEqualTo("example.org/redirect-uri"); + assertThat(login.getModes()).containsExactly("oauth2", "classic"); + SecurityProperties.Login.OAuth2 loginOAuth2 = login.getOAuth2(); + assertThat(loginOAuth2).isNotNull(); + assertThat(loginOAuth2.getClientId()).isEqualTo("example-client-id"); + assertThat(loginOAuth2.getClientSecret()).isEqualTo("example-client-secret"); + assertThat(loginOAuth2.getProvider()).isEqualTo("example-provider"); + assertThat(loginOAuth2.getRedirectUri()).isEqualTo("https://example.org/redirect-uri"); + assertThat(loginOAuth2.getIssuerUri()).isEqualTo("https://example.org/issuer-uri"); + assertThat(loginOAuth2.getAuthorizationUri()).isEqualTo("https://example.org/authorization-uri"); + assertThat(loginOAuth2.getTokenUri()).isEqualTo("https://example.org/token-uri"); + assertThat(loginOAuth2.getUserInfoUri()).isEqualTo("https://example.org/user-info-uri"); + assertThat(loginOAuth2.getJwkSetUri()).isEqualTo("https://example.org/jwk-set-uri"); + + SecurityProperties.Encryption encryption = properties.getEncryption(); + assertThat(encryption).isNotNull(); + assertThat(encryption.getSecretKey()).isEqualTo("test-test-test-test-test-test-32"); + } + + @Test + void construct_security_properties_with_null_server_is_ok() { + /* execute + test */ + assertDoesNotThrow(() -> new SecurityProperties(null, null, null)); + } + + @Test + void construct_security_properties_with_null_login_is_ok() { + /* execute + test */ + assertDoesNotThrow(() -> new SecurityProperties(mock(), null, null)); + } + + @Test + void construct_security_properties_with_login_enabled_and_null_encryption_fails() { + /* prepare */ + SecurityProperties.Login loginMock = mock(); + when(loginMock.isEnabled()).thenReturn(true); + + /* execute + test */ + /* @formatter:off */ + assertThatThrownBy(() -> new SecurityProperties(mock(), loginMock, null)) + .isInstanceOf(NullPointerException.class) + .hasMessageContaining("The property 'sechub.security.encryption' must not be null"); + /* @formatter:on */ + } + + @Test + void construct_server_properties_with_valid_arguments_succeeds() { + /* prepare */ + Set modes = Set.of("oauth2", "classic"); + SecurityProperties.Server.OAuth2 oAuth2 = mock(); + + /* execute */ + SecurityProperties.Server server = new SecurityProperties.Server(modes, oAuth2); + + /* test */ + assertThat(server.getModes()).isEqualTo(modes); + assertThat(server.isOAuth2ModeEnabled()).isTrue(); + assertThat(server.isClassicModeEnabled()).isTrue(); + assertThat(server.getOAuth2()).isEqualTo(oAuth2); + } + + @Test + void construct_server_properties_with_null_modes_fails() { + /* execute + test */ + /* @formatter:off */ + assertThatThrownBy(() -> new SecurityProperties.Server(null, mock())) + .isInstanceOf(NullPointerException.class) + .hasMessageContaining("The property 'sechub.security.server.modes' must not be null"); + /* @formatter:on */ + } + + @Test + void construct_server_properties_with_empty_modes_fails() { + /* prepare */ + Set modes = Set.of(); + + /* execute + test */ + /* @formatter:off */ + assertThatThrownBy(() -> new SecurityProperties.Server(modes, mock())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("The property 'sechub.security.server.modes' must at least include 'oauth2' or 'classic' mode"); + /* @formatter:on */ + } + + @Test + void construct_server_properties_with_invalid_modes_fails() { + /* prepare */ + Set modes = Set.of("invalid"); + + /* execute + test */ + /* @formatter:off */ + assertThatThrownBy(() -> new SecurityProperties.Server(modes, mock())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("The property 'sechub.security.server.modes' allows only 'oauth2' or 'classic' mode"); + /* @formatter:on */ + } + + @Test + void construct_server_oauth2_properties_with_jwt_mode_succeeds() { + /* prepare */ + String mode = "jwt"; + SecurityProperties.Server.OAuth2.Jwt jwt = mock(); + + /* execute */ + SecurityProperties.Server.OAuth2 oAuth2 = new SecurityProperties.Server.OAuth2(mode, jwt, null); + + /* test */ + assertThat(oAuth2.getMode()).isEqualTo(mode); + assertThat(oAuth2.isJwtModeEnabled()).isTrue(); + assertThat(oAuth2.isOpaqueTokenModeEnabled()).isFalse(); + assertThat(oAuth2.getJwt()).isEqualTo(jwt); + assertThat(oAuth2.getOpaqueToken()).isNull(); + } + + @Test + void construct_server_oauth2_properties_with_opaque_token_mode_succeeds() { + /* prepare */ + String mode = "opaque-token"; + SecurityProperties.Server.OAuth2.OpaqueToken opaqueToken = mock(); + + /* execute */ + SecurityProperties.Server.OAuth2 oAuth2 = new SecurityProperties.Server.OAuth2(mode, null, opaqueToken); + + /* test */ + assertThat(oAuth2.getMode()).isEqualTo(mode); + assertThat(oAuth2.isJwtModeEnabled()).isFalse(); + assertThat(oAuth2.isOpaqueTokenModeEnabled()).isTrue(); + assertThat(oAuth2.getJwt()).isNull(); + assertThat(oAuth2.getOpaqueToken()).isEqualTo(opaqueToken); + } + + @Test + void construct_server_oauth2_properties_with_null_mode_fails() { + /* execute + test */ + /* @formatter:off */ + assertThatThrownBy(() -> new SecurityProperties.Server.OAuth2(null, mock(), mock())) + .isInstanceOf(NullPointerException.class) + .hasMessageContaining("The property 'sechub.security.server.oauth2.mode' must not be null"); + /* @formatter:on */ + } + + @Test + void construct_server_oauth2_properties_with_invalid_mode_fails() { + /* execute + test */ + /* @formatter:off */ + assertThatThrownBy(() -> new SecurityProperties.Server.OAuth2("invalid", mock(), mock())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("The property 'sechub.security.server.oauth2.mode' allows only 'jwt' or 'opaque-token' mode"); + /* @formatter:on */ + } + + @Test + void construct_jwt_properties_with_valid_arguments_succeeds() { + /* prepare */ + String jwkSetUri = "https://example.org/jwk-set-uri"; + + /* execute */ + SecurityProperties.Server.OAuth2.Jwt jwt = new SecurityProperties.Server.OAuth2.Jwt(jwkSetUri); + + /* test */ + assertThat(jwt.getJwkSetUri()).isEqualTo(jwkSetUri); + } + + @Test + void construct_jwt_properties_with_null_jwk_set_uri_fails() { + /* execute + test */ + /* @formatter:off */ + assertThatThrownBy(() -> new SecurityProperties.Server.OAuth2.Jwt(null)) + .isInstanceOf(NullPointerException.class) + .hasMessageContaining("The property 'sechub.security.server.oauth2.jwt.jwk-set-uri' must not be null"); + /* @formatter:on */ + } + + @Test + void construct_opaque_token_properties_with_valid_arguments_succeeds() { + /* prepare */ + String introspectionUri = "https://example.org/introspection-uri"; + String clientId = "example-client-id"; + String clientSecret = "example-client-secret"; + + /* execute */ + SecurityProperties.Server.OAuth2.OpaqueToken opaqueToken = new SecurityProperties.Server.OAuth2.OpaqueToken(introspectionUri, clientId, clientSecret); + + /* test */ + assertThat(opaqueToken.getIntrospectionUri()).isEqualTo(introspectionUri); + assertThat(opaqueToken.getClientId()).isEqualTo(clientId); + assertThat(opaqueToken.getClientSecret()).isEqualTo(clientSecret); + } + + @ParameterizedTest + @ArgumentsSource(InvalidOpaqueTokenPropertiesProvider.class) + /* @formatter:off */ + void construct_opaque_token_properties_with_null_arguments_fails(String introspectionUri, + String clientId, + String clientSecret, + String errMsg) { + /* execute + test */ + assertThatThrownBy(() -> new SecurityProperties.Server.OAuth2.OpaqueToken(introspectionUri, clientId, clientSecret)) + .isInstanceOf(NullPointerException.class) + .hasMessageContaining(errMsg); + /* @formatter:on */ + } + + @Test + void construct_login_properties_with_valid_arguments_succeeds() { + /* prepare */ + boolean enabled = true; + String loginPage = "/login"; + String redirectUri = "example.org/redirect-uri"; + Set modes = Set.of("oauth2", "classic"); + SecurityProperties.Login.OAuth2 oAuth2 = mock(); + + /* execute */ + SecurityProperties.Login login = new SecurityProperties.Login(enabled, loginPage, redirectUri, modes, oAuth2); + + /* test */ + assertThat(login.isEnabled()).isEqualTo(enabled); + assertThat(login.getLoginPage()).isEqualTo(loginPage); + assertThat(login.getRedirectUri()).isEqualTo(redirectUri); + assertThat(login.getModes()).isEqualTo(modes); + assertThat(login.getOAuth2()).isEqualTo(oAuth2); + } + + @ParameterizedTest + @ArgumentsSource(InvalidLoginPropertiesProvider.class) + /* @formatter:off */ + void construct_login_properties_with_null_property_fails(Boolean enabled, + String loginPage, + String redirectUri, + Set modes, + SecurityProperties.Login.OAuth2 oAuth2, + String errMsg) { + /* execute + test */ + assertThatThrownBy(() -> new SecurityProperties.Login(enabled, loginPage, redirectUri, modes, oAuth2)) + .isInstanceOf(NullPointerException.class) + .hasMessageContaining(errMsg); + /* @formatter:on */ + } + + @Test + void construct_login_oauth2_properties_with_valid_arguments_succeeds() { + /* prepare */ + String clientId = "example-client-id"; + String clientSecret = "example-client-secret"; + String provider = "example-provider"; + String redirectUri = "https://example.org/redirect-uri"; + String issuerUri = "https://example.org/issuer-uri"; + String authorizationUri = "https://example.org/authorization-uri"; + String tokenUri = "https://example.org/token-uri"; + String userInfoUri = "https://example.org/user-info-uri"; + String jwkSetUri = "https://example.org/jwk-set-uri"; + + /* execute */ + SecurityProperties.Login.OAuth2 oAuth2 = new SecurityProperties.Login.OAuth2(clientId, clientSecret, provider, redirectUri, issuerUri, authorizationUri, + tokenUri, userInfoUri, jwkSetUri); + + /* test */ + assertThat(oAuth2.getClientId()).isEqualTo(clientId); + assertThat(oAuth2.getClientSecret()).isEqualTo(clientSecret); + assertThat(oAuth2.getProvider()).isEqualTo(provider); + assertThat(oAuth2.getRedirectUri()).isEqualTo(redirectUri); + assertThat(oAuth2.getIssuerUri()).isEqualTo(issuerUri); + assertThat(oAuth2.getAuthorizationUri()).isEqualTo(authorizationUri); + assertThat(oAuth2.getTokenUri()).isEqualTo(tokenUri); + assertThat(oAuth2.getUserInfoUri()).isEqualTo(userInfoUri); + assertThat(oAuth2.getJwkSetUri()).isEqualTo(jwkSetUri); + } + + @ParameterizedTest + @ArgumentsSource(InvalidLoginOAuth2PropertiesProvider.class) + /* @formatter:off */ + void construct_login_oauth2_properties_with_null_arguments_fails(String clientId, + String clientSecret, + String provider, + String redirectUri, + String issuerUri, + String authorizationUri, + String tokenUri, + String userInfoUri, + String jwkSetUri, + String errMsg) { + /* execute + test */ + assertThatThrownBy(() -> new SecurityProperties.Login.OAuth2(clientId, clientSecret, provider, redirectUri, issuerUri, authorizationUri, tokenUri, userInfoUri, jwkSetUri)) + .isInstanceOf(NullPointerException.class) + .hasMessageContaining(errMsg); + /* @formatter:on */ + } + + @Test + void construct_encryption_properties_with_valid_arguments_succeeds() { + /* prepare */ + String secretKey = "test-test-test-test-test-test-32"; + + /* execute */ + SecurityProperties.Encryption encryption = new SecurityProperties.Encryption(secretKey); + + /* test */ + assertThat(encryption.getSecretKey()).isEqualTo(secretKey); + } + + @Test + void construct_encryption_properties_with_null_secret_key_fails() { + /* execute + test */ + /* @formatter:off */ + assertThatThrownBy(() -> new SecurityProperties.Encryption(null)) + .isInstanceOf(NullPointerException.class) + .hasMessageContaining("The property 'sechub.security.encryption.secret-key' must not be null"); + /* @formatter:on */ + } + + @ParameterizedTest + @ValueSource(strings = { "", "1", "est-test-test-test-test-test-31", "-test-test-test-test-test-test-33" }) + void construct_aes256encryption_properties_with_non_256_bit_long_secret_key_fails(String secretKey) { + /* @formatter:off */ + /* execute & test */ + assertThatThrownBy(() -> new SecurityProperties.Encryption(secretKey)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("The property sechub.security.encryption.secret-key must be a 256-bit string"); + /* @formatter:on */ + } + + @Configuration + @EnableConfigurationProperties(SecurityProperties.class) + static class TestConfiguration { + + } + + private static class InvalidOpaqueTokenPropertiesProvider implements ArgumentsProvider { + /* @formatter:off */ + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + return Stream.of( + Arguments.of(null, "example-client-id", "example-client-secret", "The property 'sechub.security.server.oauth2.opaque-token.introspection-uri' must not be null"), + Arguments.of("https://example.org/introspection-uri", null, "example-client-secret", "The property 'sechub.security.server.oauth2.opaque-token.client-id' must not be null"), + Arguments.of("https://example.org/introspection-uri", "example-client-id", null, "The property 'sechub.security.server.oauth2.opaque-token.client-secret' must not be null") + ); + } + /* @formatter:on */ + } + + private static class InvalidLoginPropertiesProvider implements ArgumentsProvider { + /* @formatter:off */ + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + return Stream.of( + Arguments.of(null, "/login", "example.org/redirect-uri", Set.of("oauth2", "classic"), mock(SecurityProperties.Login.OAuth2.class), "The property 'sechub.security.login.enabled' must not be null"), + Arguments.of(true, null, "example.org/redirect-uri", Set.of("oauth2", "classic"), mock(SecurityProperties.Login.OAuth2.class), "The property 'sechub.security.login.login-page' must not be null"), + Arguments.of(true, "/login", null, Set.of("oauth2", "classic"), mock(SecurityProperties.Login.OAuth2.class), "The property 'sechub.security.login.redirect-uri' must not be null"), + Arguments.of(true, "/login", "example.org/redirect-uri", null, mock(SecurityProperties.Login.OAuth2.class), "The property 'sechub.security.login.modes' must not be null"), + Arguments.of(true, "/login", "example.org/redirect-uri", Set.of("oauth2", "classic"), null, "The property 'sechub.security.login.oauth2' must not be null") + ); + } + /* @formatter:on */ + } + + private static class InvalidLoginOAuth2PropertiesProvider implements ArgumentsProvider { + /* @formatter:off */ + @Override + public Stream provideArguments(ExtensionContext context) throws Exception { + return Stream.of( + Arguments.of(null, "example-client-secret", "example-provider", "https://example.org/redirect-uri", "https://example.org/issuer-uri", "https://example.org/authorization-uri", "https://example.org/token-uri", "https://example.org/user-info-uri", "https://example.org/jwk-set-uri", "The property 'sechub.security.login.oauth2.client-id' must not be null"), + Arguments.of("example-client-id", null, "example-provider", "https://example.org/redirect-uri", "https://example.org/issuer-uri", "https://example.org/authorization-uri", "https://example.org/token-uri", "https://example.org/user-info-uri", "https://example.org/jwk-set-uri", "The property 'sechub.security.login.oauth2.client-secret' must not be null"), + Arguments.of("example-client-id", "example-client-secret", null, "https://example.org/redirect-uri", "https://example.org/issuer-uri", "https://example.org/authorization-uri", "https://example.org/token-uri", "https://example.org/user-info-uri", "https://example.org/jwk-set-uri", "The property 'sechub.security.login.oauth2.provider' must not be null"), + Arguments.of("example-client-id", "example-client-secret", "example-provider", null, "https://example.org/issuer-uri", "https://example.org/authorization-uri", "https://example.org/token-uri", "https://example.org/user-info-uri", "https://example.org/jwk-set-uri", "The property 'sechub.security.login.oauth2.redirect-uri' must not be null"), + Arguments.of("example-client-id", "example-client-secret", "example-provider", "https://example.org/redirect-uri", null, "https://example.org/authorization-uri", "https://example.org/token-uri", "https://example.org/user-info-uri", "https://example.org/jwk-set-uri", "The property 'sechub.security.login.oauth2.issuer-uri' must not be null"), + Arguments.of("example-client-id", "example-client-secret", "example-provider", "https://example.org/redirect-uri", "https://example.org/issuer-uri", null, "https://example.org/token-uri", "https://example.org/user-info-uri", "https://example.org/jwk-set-uri", "The property 'sechub.security.login.oauth2.authorization-uri' must not be null"), + Arguments.of("example-client-id", "example-client-secret", "example-provider", "https://example.org/redirect-uri", "https://example.org/issuer-uri", "https://example.org/authorization-uri", null, "https://example.org/user-info-uri", "https://example.org/jwk-set-uri", "The property 'sechub.security.login.oauth2.token-uri' must not be null"), + Arguments.of("example-client-id", "example-client-secret", "example-provider", "https://example.org/redirect-uri", "https://example.org/issuer-uri", "https://example.org/authorization-uri", "https://example.org/token-uri", null, "https://example.org/jwk-set-uri", "The property 'sechub.security.login.oauth2.user-info-uri' must not be null"), + Arguments.of("example-client-id", "example-client-secret", "example-provider", "https://example.org/redirect-uri", "https://example.org/issuer-uri", "https://example.org/authorization-uri", "https://example.org/token-uri", "https://example.org/user-info-uri", null, "The property 'sechub.security.login.oauth2.jwk-set-uri' must not be null") + ); + } + } +} \ No newline at end of file diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestOAuth2JwtSecurityConfiguration.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestOAuth2JwtSecurityConfiguration.java index 06395eb7a0..2775046906 100644 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestOAuth2JwtSecurityConfiguration.java +++ b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestOAuth2JwtSecurityConfiguration.java @@ -20,9 +20,9 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.jwt.BadJwtException; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtException; /** * This configuration class provides the necessary beans to test Springs OAuth2 @@ -85,7 +85,7 @@ JwtDecoder jwtDecoder() { return builder.subject(USER_ID).build(); } - throw new JwtException("Invalid JWT token"); + throw new BadJwtException("Invalid JWT token"); }); return jwtDecoder; } diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestOAuth2OpaqueTokenSecurityConfiguration.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestOAuth2OpaqueTokenSecurityConfiguration.java index 35be940dd6..0f4abd4c79 100644 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestOAuth2OpaqueTokenSecurityConfiguration.java +++ b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestOAuth2OpaqueTokenSecurityConfiguration.java @@ -58,9 +58,11 @@ public class TestOAuth2OpaqueTokenSecurityConfiguration { /* @formatter:off */ @Autowired TestOAuth2OpaqueTokenSecurityConfiguration(RestTemplate restTemplate, - OAuth2OpaqueTokenProperties oAuth2OpaqueTokenProperties) { + SecurityProperties securityProperties) { - when(restTemplate.postForObject(eq(oAuth2OpaqueTokenProperties.getIntrospectionUri()), any(), eq(OAuth2OpaqueTokenIntrospectionResponse.class))).thenAnswer(invocation -> { + String introspectionUri = securityProperties.getServer().getOAuth2().getOpaqueToken().getIntrospectionUri(); + + when(restTemplate.postForObject(eq(introspectionUri), any(), eq(OAuth2OpaqueTokenIntrospectionResponse.class))).thenAnswer(invocation -> { HttpEntity> request = invocation.getArgument(1); String token = requireNonNull(request.getBody()).getFirst(TOKEN); boolean isActive = false; @@ -108,9 +110,9 @@ public class TestOAuth2OpaqueTokenSecurityConfiguration { /** * Here we mock the {@link UserDetailsService} to return a * {@link TestUserDetails} object based on the user id (or subject). The subject - * is determined by the {@link OAuth2OpaqueTokenIntrospector} - * component. Depending on the user id, the {@link TestUserDetails} object will - * contain the corresponding authorities. + * is determined by the {@link OAuth2OpaqueTokenIntrospector} component. + * Depending on the user id, the {@link TestUserDetails} object will contain the + * corresponding authorities. */ @Bean UserDetailsService userDetailsService() { diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestSecurityConfiguration.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestSecurityConfiguration.java index 0b44ca475e..def27107a2 100644 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestSecurityConfiguration.java +++ b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestSecurityConfiguration.java @@ -5,7 +5,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; @@ -35,22 +34,11 @@ @Configuration class TestSecurityConfiguration extends AbstractSecurityConfiguration { - private final Environment environment; - - TestSecurityConfiguration(Environment environment) { - this.environment = environment; - } - @Bean RestTemplate restTemplate() { return mock(); } - @Override - protected boolean isOAuth2Enabled() { - return environment.matchesProfiles("oauth2"); - } - @Override protected Customizer.AuthorizationManagerRequestMatcherRegistry> authorizeHttpRequests() { /* @formatter:off */ diff --git a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestSecurityController.java b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestSecurityController.java index 64bdc01153..2daee34edc 100644 --- a/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestSecurityController.java +++ b/sechub-commons-security-spring/src/test/java/com/mercedesbenz/sechub/spring/security/TestSecurityController.java @@ -51,4 +51,5 @@ String errorPage() { String actuator() { return OK; } + } diff --git a/sechub-commons-security-spring/src/test/resources/application-aes256-encryption-test.yaml b/sechub-commons-security-spring/src/test/resources/application-aes256-encryption-test.yaml deleted file mode 100644 index e96a515e4b..0000000000 --- a/sechub-commons-security-spring/src/test/resources/application-aes256-encryption-test.yaml +++ /dev/null @@ -1,4 +0,0 @@ -sechub: - security: - encryption: - secret-key: test-test-test-test-test-test-32 \ No newline at end of file diff --git a/sechub-commons-security-spring/src/test/resources/application-jwt-test.yml b/sechub-commons-security-spring/src/test/resources/application-jwt-test.yml index 2a1b6aad0b..5b1a37cf9b 100644 --- a/sechub-commons-security-spring/src/test/resources/application-jwt-test.yml +++ b/sechub-commons-security-spring/src/test/resources/application-jwt-test.yml @@ -3,7 +3,12 @@ sechub: security: server: + modes: + - oauth2 + - classic oauth2: - mode: JWT + mode: jwt jwt: jwk-set-uri: https://example.org/jwk-set-uri + encryption: + secret-key: test-test-test-test-test-test-32 \ No newline at end of file diff --git a/sechub-commons-security-spring/src/test/resources/application-login-oauth2-test.yaml b/sechub-commons-security-spring/src/test/resources/application-login-oauth2-test.yaml deleted file mode 100644 index f5c83a2c20..0000000000 --- a/sechub-commons-security-spring/src/test/resources/application-login-oauth2-test.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: MIT - -sechub: - security: - login: - enabled: true - redirect-uri: example.org/redirect-uri - modes: - - oauth2 - oauth2: - client-id: client-id - client-secret: client-secret - provider: provider - redirect-uri: redirect-uri - issuer-uri: issuer-uri - authorization-uri: authorization-uri - token-uri: token-uri - user-info-uri: user-info-uri \ No newline at end of file diff --git a/sechub-commons-security-spring/src/test/resources/application-login-test.yaml b/sechub-commons-security-spring/src/test/resources/application-login-test.yaml new file mode 100644 index 0000000000..4ce0b7c38a --- /dev/null +++ b/sechub-commons-security-spring/src/test/resources/application-login-test.yaml @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: MIT + +sechub: + security: + login: + enabled: true + login-page: /login + public-paths: + - /js/** + - /css/** + - /error + redirect-uri: example.org/redirect-uri + modes: + - oauth2 + - classic diff --git a/sechub-commons-security-spring/src/test/resources/application-opaque-token-test.yml b/sechub-commons-security-spring/src/test/resources/application-opaque-token-test.yml index bdf78a45b7..3c5675eea1 100644 --- a/sechub-commons-security-spring/src/test/resources/application-opaque-token-test.yml +++ b/sechub-commons-security-spring/src/test/resources/application-opaque-token-test.yml @@ -3,9 +3,14 @@ sechub: security: server: + modes: + - oauth2 + - classic oauth2: - mode: OPAQUE_TOKEN + mode: opaque-token opaque-token: introspection-uri: https://example.org/introspection-uri client-id: example-client-id client-secret: example-client-secret + encryption: + secret-key: test-test-test-test-test-test-32 \ No newline at end of file diff --git a/sechub-commons-security-spring/src/test/resources/application-security-properties-test.yaml b/sechub-commons-security-spring/src/test/resources/application-security-properties-test.yaml new file mode 100644 index 0000000000..fa53a5d6ce --- /dev/null +++ b/sechub-commons-security-spring/src/test/resources/application-security-properties-test.yaml @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: MIT + +sechub: + security: + server: + modes: + - oauth2 + - classic + oauth2: + mode: jwt + jwt: + jwk-set-uri: https://example.org/jwk-set-uri + login: + enabled: true + login-page: /login + redirect-uri: example.org/redirect-uri + modes: + - oauth2 + - classic + oauth2: + client-id: example-client-id + client-secret: example-client-secret + provider: example-provider + redirect-uri: https://example.org/redirect-uri + issuer-uri: https://example.org/issuer-uri + authorization-uri: https://example.org/authorization-uri + token-uri: https://example.org/token-uri + user-info-uri: https://example.org/user-info-uri + jwk-set-uri: https://example.org/jwk-set-uri + encryption: + secret-key: test-test-test-test-test-test-32 \ No newline at end of file diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/TestRestDocSecurityConfiguration.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/TestRestDocSecurityConfiguration.java index 78f1d9c9a9..d49a0ced14 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/TestRestDocSecurityConfiguration.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/TestRestDocSecurityConfiguration.java @@ -1,12 +1,16 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.restdoc; +import com.mercedesbenz.sechub.spring.security.AES256Encryption; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.web.client.RestTemplate; import com.mercedesbenz.sechub.sharedkernel.security.SecHubSecurityConfiguration; +import static org.mockito.Mockito.mock; + @Import(SecHubSecurityConfiguration.class) class TestRestDocSecurityConfiguration { @@ -14,4 +18,5 @@ class TestRestDocSecurityConfiguration { RestTemplate restTemplate() { return new RestTemplate(); } + } diff --git a/sechub-server/.gitignore b/sechub-server/.gitignore index bb48f0290a..d8fe1d1e31 100644 --- a/sechub-server/.gitignore +++ b/sechub-server/.gitignore @@ -1,2 +1,6 @@ # Dockerfile is generated by myke -*Dockerfile \ No newline at end of file +*Dockerfile + +# Ignore local property files +application-local.*.yaml +application-local.*.yml \ No newline at end of file diff --git a/sechub-server/build.gradle b/sechub-server/build.gradle index 0c3a26b419..4475fc517d 100644 --- a/sechub-server/build.gradle +++ b/sechub-server/build.gradle @@ -14,7 +14,7 @@ dependencies { api project(':sechub-server-core') implementation project(':sechub-adapter') // necessary because of adapter mocking factory methods - + implementation(library.flyway) implementation(library.apache_commons_fileupload2_core) implementation(library.apache_commons_fileupload2_jakarta) diff --git a/sechub-web-server/src/main/resources/application-local.yml b/sechub-server/src/main/resources/application-local.yml similarity index 100% rename from sechub-web-server/src/main/resources/application-local.yml rename to sechub-server/src/main/resources/application-local.yml diff --git a/sechub-server/src/main/resources/application.yml b/sechub-server/src/main/resources/application.yml index 8c6db5c83b..b80546a848 100644 --- a/sechub-server/src/main/resources/application.yml +++ b/sechub-server/src/main/resources/application.yml @@ -8,7 +8,48 @@ sechub: security: diffiehellman: length: 2048 # JDK uses per default 1024, we set here to 2048 which is more secure - + + # TODO: Albert: Move this into separate properties files + + # This will configure the SecHub Server to function as a 'resource server'. + # In resource server mode the SecHub Server will only validate tokens and not issue them. + # Validation can be done in two modes: 'jwt' or 'opaque'. + # In jwt mode the SecHub Server will validate the tokens using public keys from the issuer. + # In opaque token mode the SecHub Server will call an introspection uri to validate the token for every request. + # + # For classic mode the server will accept basic auth credentials. + server: + modes: + - oauth2 + - classic + oauth2: + mode: jwt + jwt: + jwk-set-uri: http://localhost:8080/realms/local-realm/protocol/openid-connect/certs + + # Here the SecHub Server can be configured to offer a login page. + # With this the SecHub Server will become an authentication issuer providing the client with tokens that he can + # use to authenticate against the SecHub Server for subsequent requests. + login: + enabled: true + login-page: /login + redirect-uri: https://www.google.com + modes: + - oauth2 + - classic + oauth2: + client-id: web-ui-server-local + client-secret: n8Ka5Wnkl9tv3rbPzeHt8KQ7CsEy7o5x + provider: keycloak + redirect-uri: https://localhost:8443/login/oauth2/code/keycloak + issuer-uri: http://localhost:8080/realms/local-realm + authorization-uri: http://localhost:8080/realms/local-realm/protocol/openid-connect/auth + token-uri: http://localhost:8080/realms/local-realm/protocol/openid-connect/token + user-info-uri: http://localhost:8080/realms/local-realm/protocol/openid-connect/userinfo + jwk-set-uri: http://localhost:8080/realms/local-realm/protocol/openid-connect/certs + encryption: + secret-key: aB3xYz8KpL9mQw2VcT1sNj7FuW4vEp0Z + spring: profiles: group: @@ -58,6 +99,10 @@ spring: max-request-size: 5MB resolve-lazily: true enabled: true + web: + resources: + static-locations: classpath:/static + # -------------------------------------- # - Security # -------------------------------------- diff --git a/sechub-shared-kernel/build.gradle b/sechub-shared-kernel/build.gradle index 7a7eb72a27..872ec9c577 100644 --- a/sechub-shared-kernel/build.gradle +++ b/sechub-shared-kernel/build.gradle @@ -12,7 +12,6 @@ plugins { dependencies { implementation project(':sechub-commons-security-spring') - api project(':sechub-commons-model') api project(':sechub-commons-archive') api project(':sechub-storage-core') diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/security/SecHubSecurityConfiguration.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/security/SecHubSecurityConfiguration.java index 0f771ba164..c05c372120 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/security/SecHubSecurityConfiguration.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/security/SecHubSecurityConfiguration.java @@ -1,14 +1,12 @@ package com.mercedesbenz.sechub.sharedkernel.security; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; -import com.mercedesbenz.sechub.sharedkernel.Profiles; import com.mercedesbenz.sechub.spring.security.AbstractSecurityConfiguration; @Configuration @@ -16,17 +14,6 @@ @EnableWebSecurity public class SecHubSecurityConfiguration extends AbstractSecurityConfiguration { - private final Environment environment; - - SecHubSecurityConfiguration(Environment environment) { - this.environment = environment; - } - - @Override - protected boolean isOAuth2Enabled() { - return environment.matchesProfiles(Profiles.OAUTH2); - } - @Override protected Customizer.AuthorizationManagerRequestMatcherRegistry> authorizeHttpRequests() { /* @formatter:off */ diff --git a/sechub-web-server/.gitignore b/sechub-web-server/.gitignore deleted file mode 100644 index 0fc15402e5..0000000000 --- a/sechub-web-server/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -application-local.*.yaml -application-local.*.yml \ No newline at end of file diff --git a/sechub-web-server/README.adoc b/sechub-web-server/README.adoc deleted file mode 100644 index d5b9abe2df..0000000000 --- a/sechub-web-server/README.adoc +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT -= Web Server - -WARNING: Don't use this early Web Server in production. - -== Development - -=== Standalone with mocked SecHub server access -Activate the Spring Boot Dev Profile by -starting the application using the JVM argument: - ----- --Dspring.profiles.active=web-server_dev ----- - -The started application will - -- used mocked sechub access (with mocked data) -- start with self signed certificate -- Login credentials - - User: user - - Password: password -- Class auto reloading is activated - -==== Integration test with running SecHub server ----- --Dspring.profiles.active=web-server_integrationtest ----- - -The started application will - -- use running SecHub integration test server (port 8443) -- use predefined integrationtest sechub admin credentials -- trust generated inerationtest self signed certificate -- start with self signed certificate -- Login credentials - - User: user - - Password: password - -=== Access -The Web Server is accessible at: https://localhost:4443/ diff --git a/sechub-web-server/README.md b/sechub-web-server/README.md deleted file mode 100644 index 5eba3d9a8e..0000000000 --- a/sechub-web-server/README.md +++ /dev/null @@ -1,61 +0,0 @@ - - -# SecHub Web Server - -## Overview - -SecHub Web Server is a web-based user interface for managing and interacting with the SecHub application. - -## Profiles - -To start the application locally use the `web-server_local` profile. - -This will include the following profiles: - -- `ssl-cert-provided`: a default ssl certificate will be used by the Web Server server -- `classic-auth-enabled`: enable classic login (for now with preconfigured credentials) at `/login/classic`) -- `local`: includes any local configurations matching `application-local.${USER}.yml` - -If you want to provide local configurations, create a file named `application-local.${USER}.yml` in the `src/main/resources` directory. -Make sure that the ${USER} part matches your system username. - -This will enable configurations suitable for local development and testing. - -## Running the application in OAuth2 Mode - -To run the application in OAuth2 mode, include the `oauth2` profile. - -Note: The `web-server_prod` profile includes the `oauth2` profile. - -Make sure that you either provide a valid `application-oauth2.yml` file in the `src/main/resources` directory or set the required environment variables. - -Example `application-oauth2.yml`: - -```yaml -sechub: - security: - oauth2: - client-id: example-client-id - client-secret: example-client-secret - provider: example-provider - redirect-uri: {baseUrl}/login/oauth2/code/{provider} - issuer-uri: https://sso.provider.example.org - authorization-uri: https://sso.provider.example.org/as/authorization.oauth2 - token-uri: https://sso.provider.example.org/as/token.oauth2 - user-info-uri: https://sso.provider.example.org/idp/userinfo.openid - jwk-set-uri: https://sso.provider.example.org/pf/JWKS -``` - -Alternatively, you can provide the following environment variables: - -```bash -SECHUB_SECURITY_OAUTH2_CLIENT_ID=example-client-id -SECHUB_SECURITY_OAUTH2_CLIENT_SECRET=example-client-secret -SECHUB_SECURITY_OAUTH2_PROVIDER=example-provider -SECHUB_SECURITY_OAUTH2_REDIRECT_URI={baseUrl}/login/oauth2/code/{provider} -SECHUB_SECURITY_OAUTH2_ISSUER_URI=https://sso.provider.example.org -SECHUB_SECURITY_OAUTH2_AUTHORIZATION_URI=https://sso.provider.example.org/as/authorization.oauth2 -SECHUB_SECURITY_OAUTH2_TOKEN_URI=https://sso.provider.example.org/as/token.oauth2 -SECHUB_SECURITY_OAUTH2_USER_INFO_URI=https://sso.provider.example.org/idp/userinfo.openid -SECHUB_SECURITY_OAUTH2_JWK_SET_URI=https://sso.provider.example.org/pf/JWKS -``` diff --git a/sechub-web-server/build.gradle b/sechub-web-server/build.gradle deleted file mode 100644 index 38c34fc1eb..0000000000 --- a/sechub-web-server/build.gradle +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: MIT - /*============================================================================ - * Build file for subproject - * - * Root build file: "${rootProject.projectDir}/build.gradle" - * ============================================================================ - */ - -plugins { - id 'org.springframework.boot' apply true -} - -dependencies { - implementation project(':sechub-commons-core') - implementation project(':sechub-api-java') - implementation library.springboot_starter_web - implementation library.springboot_starter_security - implementation library.springboot_starter_thymeleaf - implementation library.logstashLogbackEncoder - implementation library.thymeleaf_extras_springsecurity5 - implementation library.springboot_starter_oauth2_client - implementation library.springboot_starter_oauth2_resource_server - implementation library.springboot_starter_actuator - - testImplementation project(':sechub-testframework-spring') - testImplementation library.springboot_starter_test - testImplementation library.springframework_security_test - - developmentOnly library.springboot_devtoolssf -} - -/* make eclipse task depend on ensured local certificate*/ -tasks.eclipse.dependsOn 'ensureLocalhostCertificate' - -/** - * For integration tests, local develoment etc. we need a generated, private key - * which is different for each developer, not accidently committed to git, also - * valid on builds etc. - * - * This is done by dev-ensure_localhost_certificate.sh - for details refer to bash script - */ -task ensureLocalhostCertificate(type: Exec) { - group 'sechub' - description 'Calling this task, will ensure a localhost certificate exists. This is necessary for development and integration tests' - - workingDir "${projectDir}" - - if (OSUtil.isWindows()){ - commandLine 'cmd', '/c', 'bash', "${projectDir}/dev-ensure_localhost_certificate.sh" - }else{ - commandLine "${projectDir}/dev-ensure_localhost_certificate.sh" - } -} - -apply plugin: 'maven-publish' - -version = versionData.getWebServerVersion() - -publishing { - publications { - mavenJava(MavenPublication) { - - from components.java - - pom { - name = 'SecHub Web Server' - description = 'SecHub Web Server as a Spring Boot Jar. Ready to use.' - - scm { - url = 'https://github.com/mercedes-benz/sechub' - } - - licenses { - license { - name = 'MIT License' - url = 'https://github.com/mercedes-benz/sechub/blob/master/LICENSE' - } - } - } - } - } - - repositories { - maven { - url = project.hasProperty("mavenTargetRepoUrl") ? project.properties['mavenTargetRepoUrl'] : System.getProperty("user.home")+"/.m2/repository" - - if (project.hasProperty("mavenRepoUserName") && project.hasProperty("mavenRepoPassword")) { - credentials(PasswordCredentials) { - username project.properties['mavenRepoUserName'] - password project.properties['mavenRepoPassword'] - } - } - } - } -} \ No newline at end of file diff --git a/sechub-web-server/dev-base.sh b/sechub-web-server/dev-base.sh deleted file mode 100644 index 4f7ccb3026..0000000000 --- a/sechub-web-server/dev-base.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: MIT - -# For integration tests, local develoment etc. we need a generated, private key -# which is different for each developer, not accidently committed to git, also -# valid on builds etc. -# -# We support this by having a fixed target file path to a folder which is not tracked -# by git (except the README.md there). So even generated private keys are not accidently added -# to git... - -DEV_CERT_PATH="$(pwd)/src/main/resources/certificates-untracked" -DEV_CERT_FILE="$DEV_CERT_PATH/generated-dev-localhost-keystore.p12" - -PSEUDO_PWD="123456" - -function createLocalhostCertifcate(){ - - # - # PRECONDITION: We assume a JDK is installed and so keytool is accessible! - # - # see https://stackoverflow.com/questions/13578134/how-to-automate-keystore-generation-using-the-java-keystore-tool-w-o-user-inter - echo "Start creating localhost certificate" - keytool -genkey -alias tomcat -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore "$DEV_CERT_FILE" -validity 3650 -storepass $PSEUDO_PWD --dname "CN=localhost, OU=ID" - echo "Created file $DEV_CERT_FILE" - -} - diff --git a/sechub-web-server/dev-create_localhost_certificate.sh b/sechub-web-server/dev-create_localhost_certificate.sh deleted file mode 100755 index e8c0c2439b..0000000000 --- a/sechub-web-server/dev-create_localhost_certificate.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: MIT - -source dev-base.sh - -if [ -f "$DEV_CERT_FILE" ]; then - echo "Remove old localhost certificate" - rm "$DEV_CERT_FILE" -fi -createLocalhostCertifcate \ No newline at end of file diff --git a/sechub-web-server/dev-ensure_localhost_certificate.sh b/sechub-web-server/dev-ensure_localhost_certificate.sh deleted file mode 100755 index 60c650b531..0000000000 --- a/sechub-web-server/dev-ensure_localhost_certificate.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -# SPDX-License-Identifier: MIT - -source dev-base.sh - -if [ -f "$DEV_CERT_FILE" ]; then - exit 0 -fi -createLocalhostCertifcate \ No newline at end of file diff --git a/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/ApplicationProfiles.java b/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/ApplicationProfiles.java deleted file mode 100644 index 278f8bb203..0000000000 --- a/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/ApplicationProfiles.java +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.webserver; - -public final class ApplicationProfiles { - - public static final String BASIC_AUTH_MOCKED = "classic-auth-enabled"; - public static final String INTEGRATION_TEST_DATA = "integrationtest-data"; - public static final String LOCAL = "local"; - public static final String CLASSIC_AUTH_ENABLED = "classic-auth-enabled"; - public static final String OAUTH2_ENABLED = "oauth2"; - public static final String SSL_CERT_PROVIDED = "ssl-cert-provided"; - public static final String SSL_CERT_REQUIRED = "ssl-cert-required"; - public static final String TEST = "test"; - - private ApplicationProfiles() { - /* Prevent instantiation */ - } -} \ No newline at end of file diff --git a/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/SecHubWebServerApplication.java b/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/SecHubWebServerApplication.java deleted file mode 100644 index dbdcc6a5bb..0000000000 --- a/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/SecHubWebServerApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.webserver; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class SecHubWebServerApplication { - - public static void main(String[] args) { - SpringApplication.run(SecHubWebServerApplication.class, args); - } -} diff --git a/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/server/ManagementServerProperties.java b/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/server/ManagementServerProperties.java deleted file mode 100644 index fe83077882..0000000000 --- a/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/server/ManagementServerProperties.java +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.webserver.server; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.bind.ConstructorBinding; - -@ConfigurationProperties(prefix = ManagementServerProperties.PREFIX) -public final class ManagementServerProperties { - - static final String PREFIX = "management.server"; - - private final int port; - - @ConstructorBinding - ManagementServerProperties(int port) { - this.port = port; - } - - public int getPort() { - return port; - } -} \ No newline at end of file diff --git a/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/server/ServerProperties.java b/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/server/ServerProperties.java deleted file mode 100644 index 6e6a6e892f..0000000000 --- a/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/server/ServerProperties.java +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.webserver.server; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.bind.ConstructorBinding; - -@ConfigurationProperties(prefix = ServerProperties.PREFIX) -public final class ServerProperties { - - static final String PREFIX = "server"; - - private final int port; - - @ConstructorBinding - ServerProperties(int port) { - this.port = port; - } - - public int getPort() { - return port; - } -} \ No newline at end of file diff --git a/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/server/ServerPropertiesConfiguration.java b/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/server/ServerPropertiesConfiguration.java deleted file mode 100644 index d283e7bd91..0000000000 --- a/sechub-web-server/src/main/java/com/mercedesbenz/sechub/webserver/server/ServerPropertiesConfiguration.java +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.webserver.server; - -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -@Configuration -@EnableConfigurationProperties({ ServerProperties.class, ManagementServerProperties.class }) -public class ServerPropertiesConfiguration { - -} diff --git a/sechub-web-server/src/main/resources/application-classic-auth-enabled.yml b/sechub-web-server/src/main/resources/application-classic-auth-enabled.yml deleted file mode 100644 index bb475be945..0000000000 --- a/sechub-web-server/src/main/resources/application-classic-auth-enabled.yml +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: MIT -web-server: - client: - mocked: true - sechub: - ## Mocked sechub user (necessary for credentialinjection, we have no defaults...) - userid: "mocked-user" - apitoken: "mocked-apitoken" - -spring: - security: - user: - name: mock-user - password: mock-password \ No newline at end of file diff --git a/sechub-web-server/src/main/resources/application-integrationtest-data.yml b/sechub-web-server/src/main/resources/application-integrationtest-data.yml deleted file mode 100644 index 6d3e83fa71..0000000000 --- a/sechub-web-server/src/main/resources/application-integrationtest-data.yml +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-License-Identifier: MIT -web-server: - sechub: - ## Setup integration test sechub user (currently admin, later other role) - userid: int-test_superadmin - apitoken: int-test_superadmin-pwd \ No newline at end of file diff --git a/sechub-web-server/src/main/resources/application-ssl-cert-provided.yml b/sechub-web-server/src/main/resources/application-ssl-cert-provided.yml deleted file mode 100644 index 45a7810b64..0000000000 --- a/sechub-web-server/src/main/resources/application-ssl-cert-provided.yml +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-License-Identifier: MIT -# This configuration is used for development and local testing. It uses a self-signed certificate provided by the build. - -server: - ssl: - key-store-type: 'PKCS12' - # we use a keystore location which is never tracked by git. - # see dev-create_localhost_certificate.sh and dev-ensure_localhost_certificate.sh - key-store: 'classpath:certificates-untracked/generated-dev-localhost-keystore.p12' - key-store-password: '123456' - key-alias: 'tomcat' - -web-server: - sechub: - server-url: "https://localhost:8443" - trust-all-certificates: true diff --git a/sechub-web-server/src/main/resources/application-ssl-cert-required.yml b/sechub-web-server/src/main/resources/application-ssl-cert-required.yml deleted file mode 100644 index f9c736423f..0000000000 --- a/sechub-web-server/src/main/resources/application-ssl-cert-required.yml +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: MIT -# This configuration is used for prod and int. It requires a valid certificate to be provided through the environment variables. - -server: - ssl: - keyStoreType: - ${SECHUB_WEB_SERVER_SSL_KEYSTORE_TYPE} - key-store: - ${SECHUB_WEB_SERVER_SSL_KEYSTORE_LOCATION} - key-store-password: - ${SECHUB_WEB_SERVER_SSL_KEYSTORE_PASSWORD} - key-alias: - ${SECHUB_WEB_SERVER_SSL_KEYSTORE_ALIAS} diff --git a/sechub-web-server/src/main/resources/application.yml b/sechub-web-server/src/main/resources/application.yml deleted file mode 100644 index c97f5b09f0..0000000000 --- a/sechub-web-server/src/main/resources/application.yml +++ /dev/null @@ -1,46 +0,0 @@ -# SPDX-License-Identifier: MIT - -# Main settings -server: - port: - 4443 - ssl: - enabled: true # always enabled - protocol: TLS - enabled-protocols: TLSv1.2,TLSv1.3 - -spring: - messages: - basename: "i18n/messages" - profiles: - group: - web-server_prod: "ssl-cert-required,oauth2" - web-server_int: "ssl-cert-required,oauth2,classic-auth-enabled" - web-server_dev: "ssl-cert-provided,classic-auth-enabled" - web-server_local: "ssl-cert-provided,classic-auth-enabled,local" - web-server_test: "test" - web-server_integrationtest: "ssl-cert-provided,integrationtest-data" - web: - resources: - static-locations: classpath:/static - -# Spring Boot Actuators and Metrics -management: - server: - port: - 10250 - ssl: - enabled: false - endpoints: - web: - exposure: - include: "prometheus,health" - endpoint: - metrics: - enabled: true - prometheus: - enabled: true - prometheus: - metrics: - export: - enabled: true \ No newline at end of file diff --git a/sechub-web-server/src/main/resources/banner.txt b/sechub-web-server/src/main/resources/banner.txt deleted file mode 100644 index c071cd43f1..0000000000 --- a/sechub-web-server/src/main/resources/banner.txt +++ /dev/null @@ -1,8 +0,0 @@ - ____ _ _ _ __ __ _ ____ - / ___| ___ ___| | | |_ _| |__ \ \ / /__| |__/ ___| ___ _ ____ _____ _ __ - \___ \ / _ \/ __| |_| | | | | '_ \ \ \ /\ / / _ \ '_ \___ \ / _ \ '__\ \ / / _ \ '__| - ___) | __/ (__| _ | |_| | |_) | \ V V / __/ |_) |__) | __/ | \ V / __/ | - |____/ \___|\___|_| |_|\__,_|_.__/ \_/\_/ \___|_.__/____/ \___|_| \_/ \___|_| - -:: SecHub Web Server :: ${application.formatted-version} -:: Spring Boot :: ${spring-boot.formatted-version} \ No newline at end of file diff --git a/sechub-web-server/src/main/resources/certificates-untracked/.gitignore b/sechub-web-server/src/main/resources/certificates-untracked/.gitignore deleted file mode 100644 index a8df1b6f0c..0000000000 --- a/sechub-web-server/src/main/resources/certificates-untracked/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# accept nothing -* -# except the readme.md -!README.md -!.gitignore diff --git a/sechub-web-server/src/main/resources/certificates-untracked/README.md b/sechub-web-server/src/main/resources/certificates-untracked/README.md deleted file mode 100644 index 3172a4735d..0000000000 --- a/sechub-web-server/src/main/resources/certificates-untracked/README.md +++ /dev/null @@ -1,10 +0,0 @@ - - -# About p12 folder - -Here your p12 certificates have to be stored -- local development -- prod -- .. - -Inside this folder GIT does ignore anything except this `README.md`. \ No newline at end of file diff --git a/sechub-web-server/src/main/resources/i18n/messages.properties b/sechub-web-server/src/main/resources/i18n/messages.properties deleted file mode 100644 index 8a9f3d6b8d..0000000000 --- a/sechub-web-server/src/main/resources/i18n/messages.properties +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MIT -common.projects=Projects -common.status=Status -common.loggout=Logout - -lang.switch-de=German -lang.switch-en=English - -newapitoken.request-new=Request new API Token - -scans.project-headline-prefix=Scanned \ No newline at end of file diff --git a/sechub-web-server/src/main/resources/i18n/messages_de.properties b/sechub-web-server/src/main/resources/i18n/messages_de.properties deleted file mode 100644 index e91330ba21..0000000000 --- a/sechub-web-server/src/main/resources/i18n/messages_de.properties +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MIT -common.projects=Projekte -common.status=Status -common.loggout=Ausloggen - -lang.switch-de=Deutsch -lang.switch-en=Englisch - -newapitoken.request-new=Neues API Token anfordern - -scans.project-headline-prefix=Scans für \ No newline at end of file diff --git a/sechub-web-server/src/main/resources/logback-spring.xml b/sechub-web-server/src/main/resources/logback-spring.xml deleted file mode 100644 index 763b30380f..0000000000 --- a/sechub-web-server/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/sechub-web-server/src/main/resources/static/css/main.css b/sechub-web-server/src/main/resources/static/css/main.css deleted file mode 100644 index 9810deffeb..0000000000 --- a/sechub-web-server/src/main/resources/static/css/main.css +++ /dev/null @@ -1,166 +0,0 @@ -* { - margin: 0; - padding: 0; -} - -:root { - --color-dark: #100c08; - --color-light: #FDFDFD; - --color-grey: #696969; -} - -body { - font-family: sans-serif; - min-height: 100vh -} - -.placeholder { - color: var(--color-grey); -} - -.border-thin { - border-width: 0.2rem; - border-style: solid; -} - -.warning-border-dark-bg { - border-color: goldenrod; -} - -.warning-light-bg { - color: darkgoldenrod; -} - -.warning-dark-bg { - color: goldenrod; -} - -.container-flex { - display: flex; -} - -.flex-column { - flex-direction: column; -} - -.flex-row { - flex-direction: row; -} - -.flex-grow { - flex-grow: 1; -} - -.flex-shrink { - flex-shrink: 1; -} - -.text-align-center { - text-align: center; -} - -.padding-thin-all { - padding: 1em; -} - -.menu { - list-style: none; -} - -.menu > li { - display: inline; -} - -.item > a { - color: white; -} - -.item > a:hover { - color: gold; -} - -.item > a:visited { - color: white; -} - -.text-color-dark { - color: var(--color-dark); -} - -.text-color-light { - color: var(--color-light); -} - -.background-dark { - background: var(--color-dark); -} - -.background-light { - background: var(--color-light); -} - -.text-color-white { - color: white; -} - -.error { - color: darkred; -} - -/* ----------------- - * Info table - * ----------------- - */ - -.infoTable { - padding-bottom: 16px; - padding-top: 10px; - border-collapse: separate; - border: solid #cccccc 1px; - border-radius: 16px; - border-spacing: 0px; -} - -.infoTable th { - padding: 8px; - vertical-align: top; - text-align: left; -} - -.infoTable tr:nth-child(even) { - background: #f0f0f0; -} - -.infoTable tr:nth-child(odd) { - background: #fefefe; -} - -.infoTable td { - padding:8px; - - font-family: monospace; - vertical-align: top; - text-align: left; -} - -.infoTable td:nth-child(1) { - width: 150px; - padding-left: 10px; -} - -.infoTable td:nth-child(2) { - width: 300px; - padding-left: 10px; - border-left: 1px solid #cccccc; -} - -/* - * Logo - */ -.logo { - width: 80px; - height: 80px; -} - - - \ No newline at end of file diff --git a/sechub-web-server/src/main/resources/templates/fragments/banner.html b/sechub-web-server/src/main/resources/templates/fragments/banner.html deleted file mode 100644 index 4e813969b2..0000000000 --- a/sechub-web-server/src/main/resources/templates/fragments/banner.html +++ /dev/null @@ -1,13 +0,0 @@ - - - -Banner - - -
-
-

Early WebUI draft - do not use this in production!

-
-
- - diff --git a/sechub-web-server/src/main/resources/templates/fragments/footer.html b/sechub-web-server/src/main/resources/templates/fragments/footer.html deleted file mode 100644 index dbf51cb428..0000000000 --- a/sechub-web-server/src/main/resources/templates/fragments/footer.html +++ /dev/null @@ -1,11 +0,0 @@ - - - -Footer - - -
- SecHub Server URL: None -
- - diff --git a/sechub-web-server/src/main/resources/templates/fragments/header.html b/sechub-web-server/src/main/resources/templates/fragments/header.html deleted file mode 100644 index 28f320d080..0000000000 --- a/sechub-web-server/src/main/resources/templates/fragments/header.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - SecHub - Header - - - - - - \ No newline at end of file diff --git a/sechub-web-server/src/main/resources/templates/fragments/navbar.html b/sechub-web-server/src/main/resources/templates/fragments/navbar.html deleted file mode 100644 index 9a7a936fdd..0000000000 --- a/sechub-web-server/src/main/resources/templates/fragments/navbar.html +++ /dev/null @@ -1,32 +0,0 @@ - - - -Navbar - - - - - \ No newline at end of file diff --git a/sechub-web-server/src/main/resources/templates/home.html b/sechub-web-server/src/main/resources/templates/home.html deleted file mode 100644 index d57369166d..0000000000 --- a/sechub-web-server/src/main/resources/templates/home.html +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - SecHub WebUI - - - - -
-

Welcome to the SecHub Web-UI UNDEFINED!

- SecHub Logo -

This site is under construction. Please check back later.

-
- - \ No newline at end of file diff --git a/sechub-web-server/src/main/resources/templates/new-apitoken.html b/sechub-web-server/src/main/resources/templates/new-apitoken.html deleted file mode 100644 index d07d72e02d..0000000000 --- a/sechub-web-server/src/main/resources/templates/new-apitoken.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Projects - - - -
- -
-

Requested new API token

-

New API token sent to: mail@example.org

-

Request for new API token failed!

-
-
- - - \ No newline at end of file diff --git a/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/TestCookieRequestPostProcessor.java b/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/TestCookieRequestPostProcessor.java deleted file mode 100644 index c78a89cce9..0000000000 --- a/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/TestCookieRequestPostProcessor.java +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.webserver.security; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.test.web.servlet.request.RequestPostProcessor; - -import jakarta.servlet.http.Cookie; - -/** - * {@code TestCookieRequestPostProcessor} is a custom - * {@link RequestPostProcessor} implementation that adds a custom cookie to the - * request headers. {@link MockHttpServletRequest} . This is useful for testing - * endpoints that require cookie authentication. - * - * @author hamidonos - */ -public class TestCookieRequestPostProcessor implements RequestPostProcessor { - - private final Cookie cookie; - - public TestCookieRequestPostProcessor(Cookie cookie) { - this.cookie = cookie; - } - - @Override - @SuppressWarnings("NullableProblems") - public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - request.setCookies(cookie); - return request; - } -} \ No newline at end of file diff --git a/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/TestSecurityController.java b/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/TestSecurityController.java deleted file mode 100644 index 1aedaec910..0000000000 --- a/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/TestSecurityController.java +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.webserver.security; - -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * This controller spins up a mock API for testing the - * {@link SecurityConfiguration} of the SecHub Web UI server application. - * - * @author hamidonos - */ -@RestController -class TestSecurityController { - - private static final String OK = HttpStatus.OK.getReasonPhrase(); - - @GetMapping("/actuator") - String actuator() { - return OK; - } - - @GetMapping("/login") - String login() { - return OK; - } - - @GetMapping("/home") - String home() { - return OK; - } - - @GetMapping("/css") - String css() { - return OK; - } - - @GetMapping("/js") - String js() { - return OK; - } - - @GetMapping("/images") - String images() { - return OK; - } - - @GetMapping("/oauth2") - String oauth2() { - return OK; - } - - @GetMapping("/sechub-logo.svg") - String sechubLogoSvg() { - return OK; - } - - @GetMapping("/error") - String errorPage() { - return OK; - } -} diff --git a/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/TestWebServerSecurityConfiguration.java b/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/TestWebServerSecurityConfiguration.java deleted file mode 100644 index 41f677662a..0000000000 --- a/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/TestWebServerSecurityConfiguration.java +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.webserver.security; - -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.web.servlet.request.RequestPostProcessor; - -import com.mercedesbenz.sechub.testframework.spring.TestJwtMockAuthenticationConfiguration; -import com.mercedesbenz.sechub.webserver.encryption.AES256Encryption; - -import jakarta.servlet.http.Cookie; - -@TestConfiguration -@Import({ WebServerSecurityConfiguration.class, TestJwtMockAuthenticationConfiguration.class, OAuth2PropertiesConfig.class, AES256Encryption.class }) -public class TestWebServerSecurityConfiguration { - - @Bean - public RequestPostProcessor requestPostProcessor() { - Cookie cookie = new Cookie(TestJwtMockAuthenticationConfiguration.ACCESS_TOKEN, TestJwtMockAuthenticationConfiguration.ENCRYPTED_JWT_B64_ENCODED); - return new TestCookieRequestPostProcessor(cookie); - } -} diff --git a/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/WebServerSecurityConfigurationTest.java b/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/WebServerSecurityConfigurationTest.java deleted file mode 100644 index 52e68fb448..0000000000 --- a/sechub-web-server/src/test/java/com/mercedesbenz/sechub/webserver/security/WebServerSecurityConfigurationTest.java +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.webserver.security; - -import static org.mockito.Mockito.mock; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -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.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpStatus; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import com.mercedesbenz.sechub.testframework.spring.YamlPropertyLoaderFactory; -import com.mercedesbenz.sechub.webserver.encryption.AES256Encryption; -import com.mercedesbenz.sechub.webserver.server.ManagementServerProperties; -import com.mercedesbenz.sechub.webserver.server.ServerProperties; -import com.mercedesbenz.sechub.webserver.server.ServerPropertiesConfiguration; - -@WebMvcTest -@ActiveProfiles("classic-auth-enabled") -@TestPropertySource(locations = "classpath:application-test.yml", factory = YamlPropertyLoaderFactory.class) -class WebServerSecurityConfigurationTest { - - private final MockMvc mockMvc; - private final ServerProperties serverProperties; - private final ManagementServerProperties managementServerProperties; - - @Autowired - WebServerSecurityConfigurationTest(MockMvc mockMvc, ServerProperties serverProperties, ManagementServerProperties managementServerProperties) { - this.mockMvc = mockMvc; - this.serverProperties = serverProperties; - this.managementServerProperties = managementServerProperties; - } - - @Test - void actuator_is_accessible_anonymously_at_management_port() throws Exception { - /* prepare */ - String url = "http://localhost:%d/actuator".formatted(managementServerProperties.getPort()); - - /* execute & test */ - getAndExpect(url, HttpStatus.OK); - } - - @Test - void actuator_is_not_accessible_at_server_port() throws Exception { - /* prepare */ - String url = "http://localhost:%d/actuator".formatted(serverProperties.getPort()); - - /* execute & test */ - getAndExpect(url, HttpStatus.FORBIDDEN); - } - - @Test - void public_path_is_accessible_at_management_port() throws Exception { - /* prepare */ - String url = "http://localhost:%d/login".formatted(managementServerProperties.getPort()); - - /* execute & test */ - getAndExpect(url, HttpStatus.FORBIDDEN); - } - - @Test - void public_path_is_accessible_at_server_port() throws Exception { - /* prepare */ - String url = "http://localhost:%d/login".formatted(serverProperties.getPort()); - - /* execute & test */ - getAndExpect(url, HttpStatus.OK); - } - - private void getAndExpect(String path, HttpStatus httpStatus) throws Exception { - /* @formatter:off */ - mockMvc - .perform(MockMvcRequestBuilders.get(path)) - .andExpect(status().is(httpStatus.value())); - /* @formatter:on */ - } - - @Configuration - @Import({ WebServerSecurityConfiguration.class, ServerPropertiesConfiguration.class }) - static class TestConfig { - - @Bean - TestSecurityController testSecurityController() { - return new TestSecurityController(); - } - - @Bean - AES256Encryption aes256Encryption() { - return mock(); - } - } -} diff --git a/sechub-web-server/src/test/resources/application-test.yml b/sechub-web-server/src/test/resources/application-test.yml deleted file mode 100644 index efbc838bb7..0000000000 --- a/sechub-web-server/src/test/resources/application-test.yml +++ /dev/null @@ -1,26 +0,0 @@ -# SPDX-License-Identifier: MIT - -sechub: - security: - oauth2: - client-id: client-id - client-secret: client-secret - provider: provider - redirect-uri: redirect-uri - issuer-uri: issuer-uri - authorization-uri: authorization-uri - token-uri: token-uri - user-info-uri: user-info-uri - jwk-set-uri: https://example.org/jwk-set-uri - encryption: - secret-key: test-test-test-test-test-test-32 - -server: - port: - 4443 - ssl: - enabled: false - -management: - server: - port: 10250 \ No newline at end of file diff --git a/sechub-web/src/main/java/com/mercedesbenz/sechub/web/HomeController.java b/sechub-web/src/main/java/com/mercedesbenz/sechub/web/HomeController.java deleted file mode 100644 index 73ca38f23a..0000000000 --- a/sechub-web/src/main/java/com/mercedesbenz/sechub/web/HomeController.java +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.web; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -class HomeController { - - @GetMapping(RequestConstants.HOME) - public String home(Model model) { - return "home"; - } -} diff --git a/sechub-web/src/main/java/com/mercedesbenz/sechub/web/LoginController.java b/sechub-web/src/main/java/com/mercedesbenz/sechub/web/LoginController.java deleted file mode 100644 index 52a1ada148..0000000000 --- a/sechub-web/src/main/java/com/mercedesbenz/sechub/web/LoginController.java +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.web; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; - -import com.mercedesbenz.sechub.spring.security.LoginClassicProperties; -import com.mercedesbenz.sechub.spring.security.LoginOAuth2Properties; - -@Controller -class LoginController { - - private final LoginOAuth2Properties loginOAuth2Properties; - private final boolean isOAuth2Enabled; - private final boolean isClassicAuthEnabled; - - LoginController(@Autowired(required = false) LoginOAuth2Properties loginOAuth2Properties, - @Autowired(required = false) LoginClassicProperties loginClassicProperties) { - this.loginOAuth2Properties = loginOAuth2Properties; - this.isOAuth2Enabled = loginOAuth2Properties != null; - this.isClassicAuthEnabled = loginClassicProperties != null; - } - - @GetMapping({ RequestConstants.ROOT, RequestConstants.LOGIN }) - String login(Model model) { - model.addAttribute("isOAuth2Enabled", isOAuth2Enabled); - model.addAttribute("isClassicAuthEnabled", isClassicAuthEnabled); - - if (loginOAuth2Properties != null) { - String registrationId = loginOAuth2Properties.getProvider(); - model.addAttribute("registrationId", registrationId); - } - - return "login"; - } - -} \ No newline at end of file diff --git a/sechub-web/src/main/java/com/mercedesbenz/sechub/web/RequestConstants.java b/sechub-web/src/main/java/com/mercedesbenz/sechub/web/RequestConstants.java deleted file mode 100644 index 1e17752476..0000000000 --- a/sechub-web/src/main/java/com/mercedesbenz/sechub/web/RequestConstants.java +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.web; - -public final class RequestConstants { - - public static final String ROOT = "/"; - - public static final String LOGIN = "/login"; - public static final String HOME = "/home"; - - public static final String REQUEST_NEW_APITOKEN = "/request-new-apitoken"; -} diff --git a/sechub-web/src/main/java/com/mercedesbenz/sechub/web/RequestParamLocaleContextResolver.java b/sechub-web/src/main/java/com/mercedesbenz/sechub/web/RequestParamLocaleContextResolver.java deleted file mode 100644 index 94ebfe46c8..0000000000 --- a/sechub-web/src/main/java/com/mercedesbenz/sechub/web/RequestParamLocaleContextResolver.java +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.web; - -import java.util.List; -import java.util.Locale; - -import org.springframework.context.i18n.LocaleContext; -import org.springframework.context.i18n.SimpleLocaleContext; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.adapter.WebHttpHandlerBuilder; -import org.springframework.web.server.i18n.LocaleContextResolver; - -@Component(WebHttpHandlerBuilder.LOCALE_CONTEXT_RESOLVER_BEAN_NAME) -public class RequestParamLocaleContextResolver implements LocaleContextResolver { - public LocaleContext resolveLocaleContext(ServerWebExchange exchange) { - List lang = exchange.getRequest().getQueryParams().get("lang"); - Locale targetLocale = null; - - if (lang != null && !lang.isEmpty()) { - targetLocale = Locale.forLanguageTag(lang.get(0)); - } - if (targetLocale == null) { - targetLocale = Locale.US; - } - return new SimpleLocaleContext(targetLocale); - } - - @Override - public void setLocaleContext(ServerWebExchange exchange, LocaleContext localeContext) { - throw new UnsupportedOperationException("Cannot change lang query parameter - use a different locale context resolution strategy"); - } -} diff --git a/sechub-web/src/test/java/com/mercedesbenz/sechub/web/HomeControllerTest.java b/sechub-web/src/test/java/com/mercedesbenz/sechub/web/HomeControllerTest.java deleted file mode 100644 index 34e5c48da3..0000000000 --- a/sechub-web/src/test/java/com/mercedesbenz/sechub/web/HomeControllerTest.java +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.web; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -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.context.annotation.Import; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.RequestPostProcessor; - -import com.mercedesbenz.sechub.testframework.spring.WithMockJwtUser; -import com.mercedesbenz.sechub.testframework.spring.YamlPropertyLoaderFactory; -import com.mercedesbenz.sechub.webserver.security.TestWebServerSecurityConfiguration; -import com.mercedesbenz.sechub.webserver.server.ServerProperties; -import com.mercedesbenz.sechub.webserver.server.ServerPropertiesConfiguration; - -@WebMvcTest(HomeController.class) -@Import({ TestWebServerSecurityConfiguration.class, ServerPropertiesConfiguration.class }) -@TestPropertySource(locations = "classpath:application-test.yml", factory = YamlPropertyLoaderFactory.class) -@ActiveProfiles("oauth2") -class HomeControllerTest { - - private final MockMvc mockMvc; - private final RequestPostProcessor requestPostProcessor; - private final String homePageUrl; - - @Autowired - HomeControllerTest(MockMvc mockMvc, RequestPostProcessor requestPostProcessor, ServerProperties serverProperties) { - this.mockMvc = mockMvc; - this.requestPostProcessor = requestPostProcessor; - this.homePageUrl = "http://localhost:%d/home".formatted(serverProperties.getPort()); - } - - @Test - void home_page_is_not_accessible_anonymously() throws Exception { - /* @formatter:off */ - mockMvc - .perform(get(homePageUrl)) - .andExpect(status().is3xxRedirection()); - /* @formatter:on */ - } - - @Test - @WithMockJwtUser - void home_page_is_accessible_with_authenticated_user() throws Exception { - /* execute & test */ - - /* @formatter:off */ - mockMvc - .perform(get(homePageUrl).with(requestPostProcessor)) - .andExpect(status().isOk()) - .andReturn(); - /* @formatter:on */ - } -} diff --git a/sechub-web/src/test/java/com/mercedesbenz/sechub/web/LoginControllerClassicAuthEnabledTest.java b/sechub-web/src/test/java/com/mercedesbenz/sechub/web/LoginControllerClassicAuthEnabledTest.java deleted file mode 100644 index 2d2af3fffd..0000000000 --- a/sechub-web/src/test/java/com/mercedesbenz/sechub/web/LoginControllerClassicAuthEnabledTest.java +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.web; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -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.context.annotation.Import; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.MockMvc; - -import com.mercedesbenz.sechub.testframework.spring.YamlPropertyLoaderFactory; -import com.mercedesbenz.sechub.webserver.security.TestWebServerSecurityConfiguration; -import com.mercedesbenz.sechub.webserver.server.ServerProperties; -import com.mercedesbenz.sechub.webserver.server.ServerPropertiesConfiguration; - -@WebMvcTest(LoginController.class) -@Import({ TestWebServerSecurityConfiguration.class, ServerPropertiesConfiguration.class }) -@TestPropertySource(locations = "classpath:application-test.yml", factory = YamlPropertyLoaderFactory.class) -@ActiveProfiles("classic-auth-enabled") -class LoginControllerClassicAuthEnabledTest { - - private final MockMvc mockMvc; - private final ServerProperties serverProperties; - - @Autowired - LoginControllerClassicAuthEnabledTest(MockMvc mockMvc, ServerProperties serverProperties) { - this.mockMvc = mockMvc; - this.serverProperties = serverProperties; - } - - @Test - void login_page_is_accessible_anonymously() throws Exception { - /* @formatter:off */ - mockMvc - .perform(get("http://localhost:%d/login".formatted(serverProperties.getPort()))) - .andExpect(status().isOk()) - .andExpect(model().attributeExists("isOAuth2Enabled")) - .andExpect(model().attribute("isOAuth2Enabled", false)) - .andExpect(model().attributeExists("isClassicAuthEnabled")) - .andExpect(model().attribute("isClassicAuthEnabled", true)) - .andExpect(model().attributeDoesNotExist("registrationId")) - .andExpect(content().string(not(containsString("You will be redirected to your OAuth2 Provider for authentication.")))) - .andExpect(content().string(containsString("Login with your provided User ID & API Token."))) - .andExpect(content().string(containsString("Don't have an account? Register"))); - /* @formatter:on */ - } -} diff --git a/sechub-web/src/test/java/com/mercedesbenz/sechub/web/LoginControllerOAuth2AndClassicAuthEnabledTest.java b/sechub-web/src/test/java/com/mercedesbenz/sechub/web/LoginControllerOAuth2AndClassicAuthEnabledTest.java deleted file mode 100644 index 6aa052263b..0000000000 --- a/sechub-web/src/test/java/com/mercedesbenz/sechub/web/LoginControllerOAuth2AndClassicAuthEnabledTest.java +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.web; - -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -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.context.annotation.Import; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.MockMvc; - -import com.mercedesbenz.sechub.testframework.spring.YamlPropertyLoaderFactory; -import com.mercedesbenz.sechub.webserver.security.OAuth2Properties; -import com.mercedesbenz.sechub.webserver.security.TestWebServerSecurityConfiguration; -import com.mercedesbenz.sechub.webserver.server.ServerProperties; -import com.mercedesbenz.sechub.webserver.server.ServerPropertiesConfiguration; - -@WebMvcTest(LoginController.class) -@Import({ TestWebServerSecurityConfiguration.class, ServerPropertiesConfiguration.class }) -@TestPropertySource(locations = "classpath:application-test.yml", factory = YamlPropertyLoaderFactory.class) -@ActiveProfiles({ "oauth2", "classic-auth-enabled" }) -class LoginControllerOAuth2AndClassicAuthEnabledTest { - - private final MockMvc mockMvc; - private final OAuth2Properties oAuth2Properties; - private final ServerProperties serverProperties; - - @Autowired - LoginControllerOAuth2AndClassicAuthEnabledTest(MockMvc mockMvc, OAuth2Properties oAuth2Properties, ServerProperties serverProperties) { - this.mockMvc = mockMvc; - this.oAuth2Properties = oAuth2Properties; - this.serverProperties = serverProperties; - } - - @Test - void login_page_is_accessible_anonymously() throws Exception { - /* @formatter:off */ - mockMvc - .perform(get("http://localhost:%d/login".formatted(serverProperties.getPort()))) - .andExpect(status().isOk()) - .andExpect(model().attributeExists("isOAuth2Enabled")) - .andExpect(model().attribute("isOAuth2Enabled", true)) - .andExpect(model().attributeExists("isClassicAuthEnabled")) - .andExpect(model().attribute("isClassicAuthEnabled", true)) - .andExpect(model().attributeExists("registrationId")) - .andExpect(model().attribute("registrationId", oAuth2Properties.getProvider())) - .andExpect(content().string(containsString("You will be redirected to your OAuth2 Provider for authentication."))) - .andExpect(content().string(containsString("Login with your provided User ID & API Token."))) - .andExpect(content().string(containsString("Don't have an account? Register"))); - /* @formatter:on */ - } -} diff --git a/sechub-web/src/test/java/com/mercedesbenz/sechub/web/LoginControllerOAuth2EnabledTest.java b/sechub-web/src/test/java/com/mercedesbenz/sechub/web/LoginControllerOAuth2EnabledTest.java deleted file mode 100644 index a4da7197a6..0000000000 --- a/sechub-web/src/test/java/com/mercedesbenz/sechub/web/LoginControllerOAuth2EnabledTest.java +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.web; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -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.context.annotation.Import; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.MockMvc; - -import com.mercedesbenz.sechub.testframework.spring.YamlPropertyLoaderFactory; -import com.mercedesbenz.sechub.webserver.security.OAuth2Properties; -import com.mercedesbenz.sechub.webserver.security.TestWebServerSecurityConfiguration; -import com.mercedesbenz.sechub.webserver.server.ServerProperties; -import com.mercedesbenz.sechub.webserver.server.ServerPropertiesConfiguration; - -@WebMvcTest(LoginController.class) -@Import({ TestWebServerSecurityConfiguration.class, ServerPropertiesConfiguration.class }) -@TestPropertySource(locations = "classpath:application-test.yml", factory = YamlPropertyLoaderFactory.class) -@ActiveProfiles("oauth2") -class LoginControllerOAuth2EnabledTest { - - private final MockMvc mockMvc; - private final OAuth2Properties oAuth2Properties; - private final ServerProperties serverProperties; - - @Autowired - LoginControllerOAuth2EnabledTest(MockMvc mockMvc, OAuth2Properties oAuth2Properties, ServerProperties serverProperties) { - this.mockMvc = mockMvc; - this.oAuth2Properties = oAuth2Properties; - this.serverProperties = serverProperties; - } - - @Test - void login_page_is_accessible_anonymously() throws Exception { - /* @formatter:off */ - mockMvc - .perform(get("http://localhost:%d/login".formatted(serverProperties.getPort()))) - .andExpect(status().isOk()) - .andExpect(model().attributeExists("isOAuth2Enabled")) - .andExpect(model().attribute("isOAuth2Enabled", true)) - .andExpect(model().attributeExists("isClassicAuthEnabled")) - .andExpect(model().attribute("isClassicAuthEnabled", false)) - .andExpect(model().attributeExists("registrationId")) - .andExpect(model().attribute("registrationId", oAuth2Properties.getProvider())) - .andExpect(content().string(containsString("You will be redirected to your OAuth2 Provider for authentication."))) - .andExpect(content().string(not(containsString("Login with your provided User ID & API Token.")))) - .andExpect(content().string(not(containsString("Don't have an account? Register")))); - /* @formatter:on */ - } -}