diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index fc9c1770..cb6bcbec 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -22,10 +22,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '21' distribution: 'temurin' cache: maven - name: Runs Elasticsearch diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 895d47c8..68520db4 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -19,10 +19,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '21' distribution: 'temurin' cache: maven - name: Build with Maven @@ -39,10 +39,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '21' distribution: 'temurin' cache: maven - name: Test with Maven @@ -59,10 +59,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '21' distribution: 'temurin' cache: maven - name: Runs Elasticsearch diff --git a/Dockerfile b/Dockerfile index 32ce67b1..7c49fc29 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ # syntax=docker/dockerfile:1 -FROM eclipse-temurin:17-jdk AS builder +FROM eclipse-temurin:21-jdk AS builder WORKDIR /build RUN apt-get update && apt-get install -y maven COPY . . RUN mvn --batch-mode --update-snapshots clean package -DskipTests -FROM eclipse-temurin:17-jre AS runner +FROM eclipse-temurin:21-jre AS runner WORKDIR /app COPY --from=builder /build/target/ChannelFinder-*.jar ./channelfinder.jar CMD ["java", "-jar", "/app/channelfinder.jar", "--spring.config.name=application"] diff --git a/README.md b/README.md index be565d34..1973b2c0 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ For using docker containers there is a barebones [docker compose file](./compose * Prerequisites - * JDK 17 + * JDK 21 * Elastic version 8.11.x * **For authN/authZ using LDAP:** LDAP server, e.g. OpenLDAP @@ -62,7 +62,7 @@ Options: #### Running ```bash -sudo apt-get install openjdk-17-jre git curl wget +sudo apt-get install openjdk-21-jre git curl wget sudo systemctl start elasticsearch # Or other command to run elastic search # Replace verison with the release you want @@ -98,7 +98,7 @@ and [Eclipse](https://eclipseide.org/). * Prerequisites - * JDK 17 + * JDK 21 * Maven (via package manager or via the wrapper `./mvnw`) (version specified in [the wrapper properties](./.mvn/wrapper/maven-wrapper.properties)) diff --git a/pom.xml b/pom.xml index 4336b1ba..08b1214d 100644 --- a/pom.xml +++ b/pom.xml @@ -35,12 +35,14 @@ UTF-8 - 2.7.18 + 3.4.4 + 2.18.3 8.11.2 5.10.0 true true true + 21 ${git.commit.time} @@ -84,12 +86,12 @@ com.fasterxml.jackson.core jackson-databind - 2.16.0 + ${jackson.version} com.fasterxml.jackson.core jackson-core - 2.16.0 + ${jackson.version} jakarta.json @@ -347,10 +349,10 @@ org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.14.0 - 17 - 17 + ${java.version} + ${java.version} ${project.build.sourceEncoding} @@ -452,7 +454,7 @@ -Xdoclint:none - 17 + ${java.version} diff --git a/src/main/java/org/phoebus/channelfinder/configuration/ElasticConfig.java b/src/main/java/org/phoebus/channelfinder/configuration/ElasticConfig.java index 736e1186..a7fc37e1 100644 --- a/src/main/java/org/phoebus/channelfinder/configuration/ElasticConfig.java +++ b/src/main/java/org/phoebus/channelfinder/configuration/ElasticConfig.java @@ -24,14 +24,14 @@ import co.elastic.clients.transport.endpoints.BooleanResponse; import co.elastic.clients.transport.rest_client.RestClientTransport; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.ServletContextListener; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.Logger; -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; diff --git a/src/main/java/org/phoebus/channelfinder/configuration/WebSecurityConfig.java b/src/main/java/org/phoebus/channelfinder/configuration/WebSecurityConfig.java index 4fe197da..83a4470e 100644 --- a/src/main/java/org/phoebus/channelfinder/configuration/WebSecurityConfig.java +++ b/src/main/java/org/phoebus/channelfinder/configuration/WebSecurityConfig.java @@ -1,34 +1,50 @@ package org.phoebus.channelfinder.configuration; +import static org.springframework.security.config.Customizer.withDefaults; + +import java.util.List; +import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.*; +import org.springframework.core.env.Environment; +import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.User; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.ldap.DefaultSpringSecurityContextSource; +import org.springframework.security.ldap.authentication.BindAuthenticator; +import org.springframework.security.ldap.authentication.LdapAuthenticationProvider; import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.SecurityFilterChain; @Configuration -public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(HttpSecurity http) throws Exception { - http.csrf().disable(); - http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); - http.authorizeRequests().anyRequest().authenticated(); - http.httpBasic(); +public class WebSecurityConfig { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http.csrf(csrf -> csrf.disable()) + .sessionManagement( + session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) + .httpBasic(withDefaults()) + .build(); } - @Override - public void configure(WebSecurity web) throws Exception { + @Bean + public WebSecurityCustomizer ignoringCustomizer() { // Authentication and Authorization is only needed for non search/query operations - web.ignoring().antMatchers(HttpMethod.GET, "/**"); + return web -> web.ignoring().requestMatchers(HttpMethod.GET, "/**"); } /** External LDAP configuration properties */ @@ -83,71 +99,99 @@ public void configure(WebSecurity web) throws Exception { @Value("${file.auth.enabled:true}") boolean file_enabled; - @Override - public void configure(AuthenticationManagerBuilder auth) throws Exception { + @Bean + public AuthenticationManager authenticationManager( + AuthenticationConfiguration configuration, List providers) { + return new ProviderManager(providers); + } - if (ldap_enabled) { - DefaultSpringSecurityContextSource contextSource = - new DefaultSpringSecurityContextSource(ldap_url); - contextSource.afterPropertiesSet(); + @Bean + @ConditionalOnProperty(name = "ldap.enabled", havingValue = "true") + public AuthenticationProvider ldapAuthProvider() { - DefaultLdapAuthoritiesPopulator myAuthPopulator = - new DefaultLdapAuthoritiesPopulator(contextSource, ldap_groups_search_base); - myAuthPopulator.setGroupSearchFilter(ldap_groups_search_pattern); - myAuthPopulator.setSearchSubtree(true); - myAuthPopulator.setIgnorePartialResultException(true); + DefaultSpringSecurityContextSource contextSource = + new DefaultSpringSecurityContextSource(ldap_url); + contextSource.afterPropertiesSet(); - auth.ldapAuthentication() - .userDnPatterns(ldap_user_dn_pattern) - .ldapAuthoritiesPopulator(myAuthPopulator) - .contextSource(contextSource); - } + DefaultLdapAuthoritiesPopulator authPopulator = + new DefaultLdapAuthoritiesPopulator(contextSource, ldap_groups_search_base); + authPopulator.setGroupSearchFilter(ldap_groups_search_pattern); + authPopulator.setSearchSubtree(true); + authPopulator.setIgnorePartialResultException(true); - if (embedded_ldap_enabled) { - DefaultSpringSecurityContextSource contextSource = - new DefaultSpringSecurityContextSource(embedded_ldap_url); - contextSource.afterPropertiesSet(); - - DefaultLdapAuthoritiesPopulator myAuthPopulator = - new DefaultLdapAuthoritiesPopulator(contextSource, embedded_ldap_groups_search_base); - myAuthPopulator.setGroupSearchFilter(embedded_ldap_groups_search_pattern); - myAuthPopulator.setSearchSubtree(true); - myAuthPopulator.setIgnorePartialResultException(true); - - auth.ldapAuthentication() - .userDnPatterns(embedded_ldap_user_dn_pattern) - .ldapAuthoritiesPopulator(myAuthPopulator) - .groupSearchBase("ou=Group") - .contextSource(contextSource); - } + BindAuthenticator bindAuthenticator = new BindAuthenticator(contextSource); + bindAuthenticator.setUserDnPatterns(new String[] {ldap_user_dn_pattern}); + + return new LdapAuthenticationProvider(bindAuthenticator, authPopulator); + } + + @Bean + @ConditionalOnProperty(name = "embedded_ldap.enabled", havingValue = "true") + public AuthenticationProvider embeddedLdapAuthProvider() { + + DefaultSpringSecurityContextSource contextSource = + new DefaultSpringSecurityContextSource(embedded_ldap_url); + contextSource.afterPropertiesSet(); + + DefaultLdapAuthoritiesPopulator authPopulator = + new DefaultLdapAuthoritiesPopulator(contextSource, embedded_ldap_groups_search_base); + authPopulator.setGroupSearchFilter(embedded_ldap_groups_search_pattern); + authPopulator.setSearchSubtree(true); + authPopulator.setIgnorePartialResultException(true); - if (demo_auth_enabled) { - // read from configuration, no default content - // interpret users, pwds, roles - // user may have multiple roles - - if (demo_auth_users != null - && demo_auth_pwds != null - && demo_auth_roles != null - && demo_auth_users.length > 0 - && demo_auth_users.length == demo_auth_pwds.length - && demo_auth_pwds.length == demo_auth_roles.length) { - - for (int i = 0; i < demo_auth_users.length; i++) { - String[] userroles = demo_auth_roles[i].split(demo_auth_delimiter_roles); - if (userroles != null && userroles.length > 0) { - auth.inMemoryAuthentication() - .withUser(demo_auth_users[i]) - .password(encoder().encode(demo_auth_pwds[i])) - .roles(userroles); - } - } - } + BindAuthenticator bindAuthenticator = new BindAuthenticator(contextSource); + bindAuthenticator.setUserDnPatterns(new String[] {embedded_ldap_user_dn_pattern}); + + return new LdapAuthenticationProvider(bindAuthenticator, authPopulator); + } + + @Bean + @Conditional(EmbeddedLdapCondition.class) + public AuthenticationProvider demoAuthProvider() { + + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + PasswordEncoder encoder = encoder(); + + for (int i = 0; i < demo_auth_users.length; i++) { + String[] userroles = demo_auth_roles[i].split(demo_auth_delimiter_roles); + + manager.createUser( + User.withUsername(demo_auth_users[i]) + .password(encoder.encode(demo_auth_pwds[i])) + .roles(userroles) + .build()); } + + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(manager); + provider.setPasswordEncoder(encoder); + return provider; } @Bean public PasswordEncoder encoder() { return new BCryptPasswordEncoder(); } + + private static class EmbeddedLdapCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + + Environment environment = context.getEnvironment(); + + Boolean isDemoAuthEnabled = + environment.getProperty("demo_auth.enabled", Boolean.class, false); + String[] demoAuthPwds = environment.getProperty("demo_auth.pwds", String[].class); + String[] demoAuthRoles = environment.getProperty("demo_auth.roles", String[].class); + String[] demoAuthUsers = environment.getProperty("demo_auth.users", String[].class); + + return isDemoAuthEnabled + && !ArrayUtils.isEmpty(demoAuthUsers) + && !ArrayUtils.isEmpty(demoAuthPwds) + && !ArrayUtils.isEmpty(demoAuthRoles) + && demoAuthUsers.length == demoAuthPwds.length + && demoAuthPwds.length == demoAuthRoles.length; + } + } } diff --git a/src/main/java/org/phoebus/channelfinder/repository/ChannelRepository.java b/src/main/java/org/phoebus/channelfinder/repository/ChannelRepository.java index d2f8d903..10a4877d 100644 --- a/src/main/java/org/phoebus/channelfinder/repository/ChannelRepository.java +++ b/src/main/java/org/phoebus/channelfinder/repository/ChannelRepository.java @@ -29,6 +29,7 @@ import co.elastic.clients.json.JsonData; import co.elastic.clients.json.jackson.JacksonJsonpMapper; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PreDestroy; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; @@ -48,7 +49,6 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.StreamSupport; -import javax.annotation.PreDestroy; import org.phoebus.channelfinder.common.CFResourceDescriptors; import org.phoebus.channelfinder.common.TextUtil; import org.phoebus.channelfinder.configuration.ElasticConfig; diff --git a/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelController.java b/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelController.java index 1fbb4a99..57c3267f 100644 --- a/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelController.java +++ b/src/main/java/org/phoebus/channelfinder/rest/controller/ChannelController.java @@ -2,6 +2,7 @@ import com.google.common.collect.FluentIterable; import com.google.common.collect.Lists; +import jakarta.servlet.ServletContext; import java.text.MessageFormat; import java.util.List; import java.util.Map; @@ -10,7 +11,6 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.StreamSupport; -import javax.servlet.ServletContext; import org.phoebus.channelfinder.common.TextUtil; import org.phoebus.channelfinder.entity.Channel; import org.phoebus.channelfinder.entity.Property; diff --git a/src/main/java/org/phoebus/channelfinder/service/ChannelFinderEpicsService.java b/src/main/java/org/phoebus/channelfinder/service/ChannelFinderEpicsService.java index a1a3dc73..d553b0a1 100644 --- a/src/main/java/org/phoebus/channelfinder/service/ChannelFinderEpicsService.java +++ b/src/main/java/org/phoebus/channelfinder/service/ChannelFinderEpicsService.java @@ -1,5 +1,7 @@ package org.phoebus.channelfinder.service; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -7,8 +9,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import org.epics.pva.data.PVABoolArray; import org.epics.pva.data.PVAStringArray; import org.epics.pva.data.PVAStructure; diff --git a/src/main/java/org/phoebus/channelfinder/service/MetricsService.java b/src/main/java/org/phoebus/channelfinder/service/MetricsService.java index 0d78df12..e07ec114 100644 --- a/src/main/java/org/phoebus/channelfinder/service/MetricsService.java +++ b/src/main/java/org/phoebus/channelfinder/service/MetricsService.java @@ -4,6 +4,7 @@ import io.micrometer.core.instrument.ImmutableTag; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; +import jakarta.annotation.PostConstruct; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -12,7 +13,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; -import javax.annotation.PostConstruct; import org.phoebus.channelfinder.repository.ChannelRepository; import org.phoebus.channelfinder.repository.PropertyRepository; import org.phoebus.channelfinder.repository.TagRepository; diff --git a/src/test/java/org/phoebus/channelfinder/processors/ChannelProcessorControllerIT.java b/src/test/java/org/phoebus/channelfinder/processors/ChannelProcessorControllerIT.java index 020a5eac..c0d889cc 100644 --- a/src/test/java/org/phoebus/channelfinder/processors/ChannelProcessorControllerIT.java +++ b/src/test/java/org/phoebus/channelfinder/processors/ChannelProcessorControllerIT.java @@ -5,6 +5,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.util.Base64; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -21,7 +22,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.util.Base64Utils; @ExtendWith(SpringExtension.class) @WebMvcTest(ChannelProcessorController.class) @@ -31,7 +31,7 @@ class ChannelProcessorControllerIT { protected static final String AUTHORIZATION = - "Basic " + Base64Utils.encodeToString("admin:adminPass".getBytes()); + "Basic " + Base64.getEncoder().encodeToString("admin:adminPass".getBytes()); @Autowired protected MockMvc mockMvc; @MockBean IChannelScroll channelScroll; diff --git a/src/test/resources/Dockerfile b/src/test/resources/Dockerfile index 959ae56d..69f0bcf6 100644 --- a/src/test/resources/Dockerfile +++ b/src/test/resources/Dockerfile @@ -16,7 +16,7 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ------------------------------------------------------------------------------ -FROM eclipse-temurin:17-jre +FROM eclipse-temurin:21-jre # deployment unit COPY ../../../target/ChannelFinder-*.jar /channelfinder/ChannelFinder-*.jar