diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/AbstractSecurityConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/AbstractSecurityConfiguration.java index f2b60a37f7..4af7f0f697 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/AbstractSecurityConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/AbstractSecurityConfiguration.java @@ -220,16 +220,16 @@ protected String[] getPatterns() { } protected void configureSession(HttpSecurity http) throws Exception { - if (redisProperties.isEnabledLimitSession()) { + if (redisProperties.getSession().isEnabledLimitSession()) { http.sessionManagement(httpSecuritySessionManagementConfigurer -> { - httpSecuritySessionManagementConfigurer.maximumSessions(redisProperties.getMaxSession()); + httpSecuritySessionManagementConfigurer.maximumSessions(redisProperties.getSession().getMaxSession()); httpSecuritySessionManagementConfigurer.sessionFixation().newSession(); }); } } protected void configureFilters(HttpSecurity http) { - if (redisProperties.isEnabledFilter()) { + if (redisProperties.getSession().isEnabledFilter()) { // http.addFilterBefore(new LoginAttemptsFilter(), ChannelProcessingFilter.class); } } @@ -252,4 +252,4 @@ protected boolean existConfigurationHeaders() { protected abstract SecurityConfigurationProperties getSecurityConfigProperties(); -} \ No newline at end of file +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/SessionConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/SessionConfiguration.java index 2c1b6ec787..b25c91a3e6 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/SessionConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/SessionConfiguration.java @@ -2,22 +2,25 @@ import com.netgrif.application.engine.configuration.properties.DataConfigurationProperties; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisNode; +import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.security.web.session.HttpSessionEventPublisher; -import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisIndexedHttpSession; import org.springframework.session.web.http.HeaderHttpSessionIdResolver; import org.springframework.session.web.http.HttpSessionIdResolver; -import static org.bouncycastle.cms.RecipientId.password; +import java.util.List; +@Slf4j @Configuration -@EnableRedisIndexedHttpSession(redisNamespace = "spring:session:${netgrif.engine.data.redis.namespace}") +@EnableRedisIndexedHttpSession(redisNamespace = "spring:session:${netgrif.engine.data.redis.session.namespace}") @ConditionalOnProperty( value = "netgrif.engine.security.static.enabled", havingValue = "false", @@ -30,14 +33,62 @@ public class SessionConfiguration { @Bean public JedisConnectionFactory jedisConnectionFactory() { + if (redisProperties.getSentinel().getMaster() != null && !redisProperties.getSentinel().getMaster().isEmpty()) { + return redisSentinelConfiguration(); + + } else { + return standaloneRedisConfiguration(); + } + } + + protected JedisConnectionFactory standaloneRedisConfiguration() { String hostName = redisProperties.getHost() == null ? "localhost" : redisProperties.getHost(); - int port = redisProperties.getPort() == 0 ? 6379 : redisProperties.getPort(); + int port = redisProperties.getPort() == 0 ? RedisNode.DEFAULT_PORT : redisProperties.getPort(); RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(hostName, port); - if(redisProperties.getUsername() != null && redisProperties.getPassword() !=null && !redisProperties.getUsername().isEmpty() && !redisProperties.getPassword().isEmpty()){ + if (hasCredentials(redisProperties.getUsername(), redisProperties.getPassword())) { redisStandaloneConfiguration.setUsername(redisProperties.getUsername()); redisStandaloneConfiguration.setPassword(redisProperties.getPassword()); } - return new JedisConnectionFactory(redisStandaloneConfiguration); + JedisClientConfiguration clientConfiguration = jedisClientConfiguration(); + return new JedisConnectionFactory(redisStandaloneConfiguration, clientConfiguration); + } + + protected JedisConnectionFactory redisSentinelConfiguration() { + RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration(); + sentinelConfiguration.setMaster(redisProperties.getSentinel().getMaster()); + List nodes = redisProperties.getSentinel().getNodes().stream().map(node -> { + try { + return RedisNode.fromString(node); + } catch (Exception e) { + log.warn("Parsing redis sentinel node {} has failed. Trying to use the value as an address without port and adding default sentinel port {}", node, RedisNode.DEFAULT_SENTINEL_PORT, e); + return new RedisNode(node, RedisNode.DEFAULT_SENTINEL_PORT); + } + }).toList(); + sentinelConfiguration.setSentinels(nodes); + + if (hasCredentials(redisProperties.getUsername(), redisProperties.getPassword())) { + sentinelConfiguration.setUsername(redisProperties.getUsername()); + sentinelConfiguration.setPassword(redisProperties.getPassword()); + } + if (hasCredentials(redisProperties.getSentinel().getUsername(), redisProperties.getSentinel().getPassword())) { + sentinelConfiguration.setSentinelUsername(redisProperties.getSentinel().getUsername()); + sentinelConfiguration.setSentinelPassword(redisProperties.getSentinel().getPassword()); + } + + JedisClientConfiguration clientConfiguration = jedisClientConfiguration(); + return new JedisConnectionFactory(sentinelConfiguration, clientConfiguration); + } + + protected JedisClientConfiguration jedisClientConfiguration() { + if (redisProperties.isSsl()) { + return JedisClientConfiguration.builder().useSsl().build(); + } + return JedisClientConfiguration.defaultConfiguration(); + } + + private boolean hasCredentials(String username, String password) { + return username != null && !username.isBlank() && + password != null && !password.isBlank(); } @Bean @@ -50,4 +101,4 @@ public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); } -} \ No newline at end of file +} diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/SessionConfigurationStaticEnabled.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/SessionConfigurationStaticEnabled.java index 6e34029984..711eaed717 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/SessionConfigurationStaticEnabled.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/SessionConfigurationStaticEnabled.java @@ -9,7 +9,7 @@ import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisIndexedHttpSession; @Configuration -@EnableRedisIndexedHttpSession(redisNamespace = "spring:session:${spring.session.redis.namespace}") +@EnableRedisIndexedHttpSession(redisNamespace = "spring:session:${netgrif.engine.data.redis.session.namespace}") @ConditionalOnProperty( value = "netgrif.engine.security.static.enabled", havingValue = "true" diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/DataConfigurationProperties.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/DataConfigurationProperties.java index f248efda4e..ed4ba5cf5f 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/DataConfigurationProperties.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/DataConfigurationProperties.java @@ -16,6 +16,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.Resource; +import org.springframework.data.redis.connection.RedisNode; import org.springframework.data.elasticsearch.core.RefreshPolicy; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -107,8 +108,9 @@ public ElasticsearchProperties elasticsearchProperties() { @Bean @Primary public RedisProperties redisProperties() { - if (redis.getNamespace() == null) { - redis.setNamespace(databaseName); + String namespace = redis.getSession().getNamespace(); + if (namespace == null || namespace.isBlank() || "spring:session".equals(namespace)) { + redis.getSession().setNamespace("spring:session:" + databaseName); } return redis; } @@ -189,7 +191,7 @@ public static class MongoProperties extends org.springframework.boot.autoconfigu /** * Specifies the maximum number of connections that can be initiated concurrently. * This property is used to throttle the number of simultaneous connection attempts - * to limit resource usage and prevent connection saturation. + * to limit resource usage and prevent connection saturation. */ private int maxConnecting = 2; @@ -523,7 +525,7 @@ public static class ElasticsearchProperties { */ @Valid private BatchProperties batch = new BatchProperties(); - + /** * Configuration properties for handling queues in Elasticsearch operations. @@ -709,17 +711,15 @@ public static class QueueProperties { } } + /** - * Configuration properties for Redis session management in the application. - *

- * This class extends {@link RedisSessionProperties}, providing additional - * configurations specific to Redis-based session handling in the application. - * It allows customization of connection details, session limiting, and other Redis-specific settings. + * Represents configuration properties for Redis used within the application. + * This class contains configurations related to the Redis server, including its + * connection details, sentinel and cluster settings, and session management properties. */ @Data - @EqualsAndHashCode(callSuper = true) - @ConfigurationProperties(prefix = "netgrif.engine.session") - public static class RedisProperties extends RedisSessionProperties { + @ConfigurationProperties(prefix = "netgrif.engine.data.redis") + public static class RedisProperties { /** * Hostname or IP address of the Redis server. @@ -731,7 +731,7 @@ public static class RedisProperties extends RedisSessionProperties { * Port number for connecting to the Redis server. * Default value is {@code 6379}. */ - private int port = 6379; + private int port = RedisNode.DEFAULT_PORT; /** * Username for authenticating with the Redis server. @@ -746,22 +746,101 @@ public static class RedisProperties extends RedisSessionProperties { private String password; /** - * Flag indicating whether to enable session limit functionality. - * If {@code true}, sessions will be limited based on the configured {@link #maxSession} value. + * Indicates whether SSL (Secure Sockets Layer) is enabled for connections. + * Set to {@code true} to enable SSL or {@code false} to disable it. + * This property is primarily used for configuring secure communication + * with a Redis server. + */ + private boolean ssl = false; + + /** + * Configuration properties for Redis Sentinel. + *

+ * This property defines the settings required for connecting to a Redis Sentinel + * setup. It includes information about the master node, a list of sentinel nodes, + * and optional authentication credentials such as username and password. */ - private boolean enabledLimitSession = false; + private RedisSentinelProperties sentinel = new RedisSentinelProperties(); /** - * Maximum number of sessions allowed per user when session limiting is enabled. - * Default value is {@code 1}. + * Configuration property for managing Redis-based session settings for this application. + * Uses the {@link EngineRedisSessionProperties} class to define specific session handling configurations. + * Allows customization of session behavior such as session limiting and filtering. */ - private int maxSession = 1; + private EngineRedisSessionProperties session = new EngineRedisSessionProperties(); /** - * Flag indicating whether Redis filtering is enabled. - * Default value is {@code false}. + * Represents configuration properties for Redis Sentinel. + * This class is typically used to configure and connect to a Redis Sentinel setup + * by specifying the master node and the sentinel nodes involved. */ - private boolean enabledFilter = false; + @Data + public static class RedisSentinelProperties { + + public static final String DEFAULT_SENTINEL_NODE = "localhost:" + RedisNode.DEFAULT_SENTINEL_PORT; + + /** + * The name of the Redis master node to which Redis Sentinel clients should connect. + * Specifies the master node in a Redis Sentinel deployment that is responsible for + * managing the data and serving read/write queries. + * This variable is essential for identifying the Redis master node among the available + * nodes in the Sentinel setup. + */ + private String master; + + /** + * A list of Redis Sentinel nodes used for connection. + * Each node in the list should be in the format of "host:port". + * By default, this list contains a single node pointing to "localhost:26379". + * In a Redis Sentinel setup, multiple nodes can be specified to ensure high availability and fault tolerance. + */ + private List nodes = List.of(DEFAULT_SENTINEL_NODE); + + /** + * The username used for authentications or configurations related to Redis Sentinel properties. + * This variable can be used to specify an optional username for connecting to a Redis database + * when authentication is configured to require one. + */ + private String username; + + /** + * The password used for authentication with the Redis Sentinel setup. + * This variable specifies the password needed to connect to the Redis database + * when the configuration requires authentication for access. + * It ensures secure communication and prevents unauthorized access to the database. + */ + private String password; + } + + /** + * Configuration properties for Redis session management in the application. + *

+ * This class extends {@link RedisSessionProperties}, providing additional + * configurations specific to Redis-based session handling in the application. + * It allows session limiting and other Redis-specific session settings. + */ + @Data + @EqualsAndHashCode(callSuper = true) + public static class EngineRedisSessionProperties extends RedisSessionProperties { + + /** + * Flag indicating whether to enable session limit functionality. + * If {@code true}, sessions will be limited based on the configured {@link #maxSession} value. + */ + private boolean enabledLimitSession = false; + + /** + * Maximum number of sessions allowed per user when session limiting is enabled. + * Default value is {@code 1}. + */ + private int maxSession = 1; + + /** + * Flag indicating whether Redis filtering is enabled. + * Default value is {@code false}. + */ + private boolean enabledFilter = false; + } } /** diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/ImpersonationConfigurationProperties.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/ImpersonationConfigurationProperties.java index ab5c80cdc3..e91a509deb 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/ImpersonationConfigurationProperties.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/ImpersonationConfigurationProperties.java @@ -19,7 +19,7 @@ public class ImpersonationConfigurationProperties { * The Redis namespace used for storing impersonation configurations. * This value is resolved from the "redisProperties.namespace" SpEL expression. */ - @Value("#{redisProperties.namespace}") + @Value("#{redisProperties.session.namespace}") private String redisNamespace; /** diff --git a/application-engine/src/main/java/com/netgrif/application/engine/manager/service/SessionManagerService.java b/application-engine/src/main/java/com/netgrif/application/engine/manager/service/SessionManagerService.java index 8cfbbef942..de5fed1738 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/manager/service/SessionManagerService.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/manager/service/SessionManagerService.java @@ -28,7 +28,7 @@ public class SessionManagerService implements ISessionManagerService { public SessionManagerService(RedisIndexedSessionRepository repository, SessionRegistry sessionRegistry, DataConfigurationProperties.RedisProperties redisProperties) { this.repository = repository; this.sessionRegistry = sessionRegistry; - this.redisUsernameKey = RedisIndexedSessionRepository.DEFAULT_NAMESPACE + ":" + redisProperties.getNamespace() + ":index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:"; + this.redisUsernameKey = RedisIndexedSessionRepository.DEFAULT_NAMESPACE + ":" + redisProperties.getSession().getNamespace() + ":index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:"; } @Override diff --git a/application-engine/src/main/resources/application.yaml b/application-engine/src/main/resources/application.yaml index 8e91af58c6..a2a41fef93 100644 --- a/application-engine/src/main/resources/application.yaml +++ b/application-engine/src/main/resources/application.yaml @@ -23,7 +23,8 @@ netgrif: case: ${NETGRIF_ENGINE_DATA_DATABASE_NAME:nae}_case task: ${NETGRIF_ENGINE_DATA_DATABASE_NAME:nae}_task redis: - namespace: nae + session: + namespace: ${NETGRIF_ENGINE_DATA_REDIS_SESSION_NAMESPACE:netgrif} host: ${NETGRIF_ENGINE_DATA_REDIS_HOST:localhost} port: ${NETGRIF_ENGINE_DATA_REDIS_PORT:6379} jackson: