Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -252,4 +252,4 @@ protected boolean existConfigurationHeaders() {

protected abstract SecurityConfigurationProperties getSecurityConfigProperties();

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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<RedisNode> 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
Expand All @@ -50,4 +101,4 @@ public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Comment on lines +111 to 114
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid double spring:session prefix in fallback.

Now that EnableRedisIndexedHttpSession already expands the namespace as spring:session:${...}, this fallback still prepends spring:session: when the bound value is the parent default (spring:session). In configs that omit netgrif.engine.data.redis.session.namespace, you’ll end up with spring:session:spring:session:<dbName>, so the static session config and the runtime repository will read/write different keys. Please leave the fallback as the bare suffix (e.g., set it to databaseName) and let the annotation (and any other callers) add the spring:session: prefix exactly once.

🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/DataConfigurationProperties.java
around lines 109 to 112, the fallback currently prepends "spring:session:"
producing duplicates when the bound value equals the parent default
"spring:session"; change the fallback to set only the bare databaseName (i.e.,
set namespace to databaseName) so the framework/annotation can apply the
"spring:session:" prefix exactly once, leaving the conditional logic
(null/blank/"spring:session") intact.

return redis;
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -523,7 +525,7 @@ public static class ElasticsearchProperties {
*/
@Valid
private BatchProperties batch = new BatchProperties();


/**
* Configuration properties for handling queues in Elasticsearch operations.
Expand Down Expand Up @@ -709,17 +711,15 @@ public static class QueueProperties {
}
}


/**
* Configuration properties for Redis session management in the application.
* <p>
* 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.
Expand All @@ -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.
Expand All @@ -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.
* <p>
* 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<String> 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.
* <p>
* 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;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update the outdated comment.

The comment references the old SpEL expression path. It should be updated to reflect the new nested structure.

Apply this diff to update the comment:

-     * This value is resolved from the "redisProperties.namespace" SpEL expression.
+     * This value is resolved from the "redisProperties.session.namespace" SpEL expression.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* This value is resolved from the "redisProperties.namespace" SpEL expression.
* This value is resolved from the "redisProperties.session.namespace" SpEL expression.
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/ImpersonationConfigurationProperties.java
around line 20, update the outdated comment that references the old SpEL path
"redisProperties.namespace" to the new nested SpEL path
"redis.properties.namespace"; replace the string in the comment so it accurately
reflects the new property structure.

*/
@Value("#{redisProperties.namespace}")
@Value("#{redisProperties.session.namespace}")
private String redisNamespace;
Comment on lines +22 to 23
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the redisProperties bean structure supports the new nested path.

The SpEL expression change aligns with the broader refactoring to nest session-related Redis properties. However, ensure that:

  1. The redisProperties bean (likely defined in DataConfigurationProperties.java) has the nested session.namespace property path
  2. Migration documentation exists for this breaking change to guide users in updating their configuration

Run the following script to verify that the redisProperties bean structure includes the nested session namespace:

Additionally, verify that migration documentation or release notes exist for this breaking change:


🏁 Script executed:

#!/bin/bash
# Description: Verify redisProperties bean has session.namespace structure

# Search for the RedisProperties class definition and its session property
ast-grep --pattern $'class $_ {
  $$$
  session
  $$$
}'

# Also search for getSession or setSession methods in properties classes
rg -n -C5 --type=java 'getSession|setSession|class.*Properties' application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/

Length of output: 105172


🏁 Script executed:

#!/bin/bash
# Search for migration docs or changelog entries
fd -e md . | xargs rg -l 'migration|breaking.*change|Redis.*Sentinel|session.*namespace' --type=md

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Check CHANGELOG.md for breaking change documentation
cat application-engine/CHANGELOG.md | head -100

Length of output: 3686



🏁 Script executed:

#!/bin/bash
# Search CHANGELOG for redis and session related entries
rg -n 'redis|session|namespace' application-engine/CHANGELOG.md -i

Length of output: 311



🏁 Script executed:

#!/bin/bash
# Search for migration guides or breaking change documentation
fd -e md -e rst . | xargs grep -l "breaking\|migration\|upgrade" 2>/dev/null | head -20

Length of output: 65


Document this breaking change in the CHANGELOG and provide migration guidance.

The SpEL expression change is structurally correct—the redisProperties bean structure supports the nested session.namespace path via EngineRedisSessionProperties. However, this is a breaking change that requires explicit user guidance:

  • Add a breaking change entry to CHANGELOG.md documenting the migration path from redisProperties.namespace to redisProperties.session.namespace
  • Ensure users know to update their configuration: netgrif.engine.data.redis.session.namespace is now the correct property path
  • Clarify the impact on existing deployments, especially multi-cluster setups where session sharing is configured
🤖 Prompt for AI Agents
In
application-engine/src/main/java/com/netgrif/application/engine/configuration/properties/ImpersonationConfigurationProperties.java
around lines 22 to 23, document the breaking change caused by switching the SpEL
to use redisProperties.session.namespace: add a breaking-change entry to
CHANGELOG.md that explains the property rename from redisProperties.namespace to
redisProperties.session.namespace, provide explicit migration steps telling
users to update their configs to netgrif.engine.data.redis.session.namespace,
and call out the operational impact (especially for
multi-cluster/session-sharing setups) with recommendations for validating
session isolation after migration.


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion application-engine/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading