From d0f17b167ea0023e2c7c92abb0001962c007bc7b Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 7 Jan 2022 11:04:58 +0100 Subject: [PATCH] Polishing. Reformat code. Consider sentinel username in JedisConnectionFactory. Update years in license headers. See #2218 Original pull request: #2224. --- src/main/asciidoc/index.adoc | 2 +- src/main/asciidoc/new-features.adoc | 5 +++ src/main/asciidoc/reference/redis.adoc | 1 + .../redis/connection/RedisConfiguration.java | 33 ++++++++------- .../RedisSentinelConfiguration.java | 8 +++- .../jedis/JedisConnectionFactory.java | 7 ++-- .../connection/lettuce/LettuceConverters.java | 8 ++-- .../RedisSentinelConfigurationUnitTests.java | 16 +++++--- .../JedisConnectionFactoryUnitTests.java | 16 +++++++- .../lettuce/LettuceConvertersUnitTests.java | 41 ++++++++++++++----- 10 files changed, 95 insertions(+), 42 deletions(-) diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index 69d9853ee..9265c9805 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -6,7 +6,7 @@ ifdef::backend-epub3[:front-cover-image: image:epub-cover.png[Front Cover,1050,1 :spring-data-commons-include: ../../../../spring-data-commons/src/main/asciidoc :spring-data-commons-docs: https://raw.githubusercontent.com/spring-projects/spring-data-commons/master/src/main/asciidoc -(C) 2011-2021 The original authors. +(C) 2011-2022 The original authors. NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 428aa0dc6..14db73698 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -3,6 +3,11 @@ This section briefly covers items that are new and noteworthy in the latest releases. +[[new-in-2.7.0]] +== New in Spring Data Redis 2.7 + +* Sentinel ACL authentication considering a sentinel-specific username. Setting a username enables username and password authentication requiring Redis 6. + [[new-in-2.6.0]] == New in Spring Data Redis 2.6 diff --git a/src/main/asciidoc/reference/redis.adoc b/src/main/asciidoc/reference/redis.adoc index 691e101a6..c0976e906 100644 --- a/src/main/asciidoc/reference/redis.adoc +++ b/src/main/asciidoc/reference/redis.adoc @@ -319,6 +319,7 @@ public RedisConnectionFactory lettuceConnectionFactory() { .Configuration Properties * `spring.redis.sentinel.master`: name of the master node. * `spring.redis.sentinel.nodes`: Comma delimited list of host:port pairs. +* `spring.redis.sentinel.username`: The username to apply when authenticating with Redis Sentinel (requires Redis 6) * `spring.redis.sentinel.password`: The password to apply when authenticating with Redis Sentinel ==== diff --git a/src/main/java/org/springframework/data/redis/connection/RedisConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisConfiguration.java index dae6dc168..54d508b02 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisConfiguration.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -400,6 +400,23 @@ public interface RedisConfiguration { return getPassword(); } + /** + * Create and set a username with the given {@link String}. Requires Redis 6 or newer. + * + * @param sentinelUsername the username for sentinel. + * @since 2.7 + */ + void setSentinelUsername(@Nullable String sentinelUsername); + + /** + * Get the username to use when connecting. + * + * @return {@literal null} if none set. + * @since 2.7 + */ + @Nullable + String getSentinelUsername(); + /** * Create and set a {@link RedisPassword} to be used when authenticating with Redis Sentinel from the given * {@link String}. @@ -440,20 +457,6 @@ public interface RedisConfiguration { */ RedisPassword getSentinelPassword(); - /** - * Create and set a username with the given {@link String}. Requires Redis 6 or newer. - * - * @param sentinelUsername the username for sentinel. - */ - void setSentinelUsername(@Nullable String sentinelUsername); - - /** - * Get the username to use when connecting. - * - * @return {@literal null} if none set. - */ - @Nullable - String getSentinelUsername(); } /** diff --git a/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java b/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java index 219c77b3c..9b9e8d0ec 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisSentinelConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,8 +46,8 @@ public class RedisSentinelConfiguration implements RedisConfiguration, SentinelC private static final String REDIS_SENTINEL_MASTER_CONFIG_PROPERTY = "spring.redis.sentinel.master"; private static final String REDIS_SENTINEL_NODES_CONFIG_PROPERTY = "spring.redis.sentinel.nodes"; - private static final String REDIS_SENTINEL_PASSWORD_CONFIG_PROPERTY = "spring.redis.sentinel.password"; private static final String REDIS_SENTINEL_USERNAME_CONFIG_PROPERTY = "spring.redis.sentinel.username"; + private static final String REDIS_SENTINEL_PASSWORD_CONFIG_PROPERTY = "spring.redis.sentinel.password"; private @Nullable NamedNode master; private Set sentinels; @@ -343,6 +343,9 @@ public class RedisSentinelConfiguration implements RedisConfiguration, SentinelC if (!ObjectUtils.nullSafeEquals(dataNodePassword, that.dataNodePassword)) { return false; } + if (!ObjectUtils.nullSafeEquals(sentinelUsername, that.sentinelUsername)) { + return false; + } return ObjectUtils.nullSafeEquals(sentinelPassword, that.sentinelPassword); } @@ -357,6 +360,7 @@ public class RedisSentinelConfiguration implements RedisConfiguration, SentinelC result = 31 * result + database; result = 31 * result + ObjectUtils.nullSafeHashCode(dataNodeUsername); result = 31 * result + ObjectUtils.nullSafeHashCode(dataNodePassword); + result = 31 * result + ObjectUtils.nullSafeHashCode(sentinelUsername); result = 31 * result + ObjectUtils.nullSafeHashCode(sentinelPassword); return result; } diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java index 272daa543..c77be1d64 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2021 the original author or authors. + * Copyright 2011-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -360,8 +360,9 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, this.initialized = true; } - private JedisClientConfig createSentinelClientConfig(SentinelConfiguration sentinelConfiguration) { - return createClientConfig(0, null, sentinelConfiguration.getSentinelPassword()); + JedisClientConfig createSentinelClientConfig(SentinelConfiguration sentinelConfiguration) { + return createClientConfig(0, sentinelConfiguration.getSentinelUsername(), + sentinelConfiguration.getSentinelPassword()); } private JedisClientConfig createClientConfig(int database, @Nullable String username, RedisPassword password) { diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java index 387a877df..f54c3f3a0 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2021 the original author or authors. + * Copyright 2013-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -516,7 +516,7 @@ public abstract class LettuceConverters extends Converters { String sentinelUsername = sentinelConfiguration.getSentinelUsername(); if (StringUtils.hasText(sentinelUsername) && sentinelPassword.isPresent()) { // See https://github.com/lettuce-io/lettuce-core/issues/1404 - sentinelBuilder.withAuthentication(sentinelUsername, new String(sentinelPassword.toOptional().orElse((new char[0])))); + sentinelBuilder.withAuthentication(sentinelUsername, sentinelPassword.get()); } else { sentinelPassword.toOptional().ifPresent(sentinelBuilder::withPassword); } @@ -527,9 +527,9 @@ public abstract class LettuceConverters extends Converters { String username = sentinelConfiguration.getUsername(); RedisPassword password = sentinelConfiguration.getPassword(); - if (StringUtils.hasText(username)) { + if (StringUtils.hasText(username) && password.isPresent()) { // See https://github.com/lettuce-io/lettuce-core/issues/1404 - builder.withAuthentication(username, new String(password.toOptional().orElse(new char[0]))); + builder.withAuthentication(username, password.get()); } else { password.toOptional().ifPresent(builder::withPassword); } diff --git a/src/test/java/org/springframework/data/redis/connection/RedisSentinelConfigurationUnitTests.java b/src/test/java/org/springframework/data/redis/connection/RedisSentinelConfigurationUnitTests.java index e90dc6c73..fc7ae87d5 100644 --- a/src/test/java/org/springframework/data/redis/connection/RedisSentinelConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/RedisSentinelConfigurationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * Copyright 2015-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,11 +23,12 @@ import java.util.HashSet; import org.junit.jupiter.api.Test; -import org.springframework.core.env.PropertySource; import org.springframework.mock.env.MockPropertySource; import org.springframework.util.StringUtils; /** + * Unit tests for {@link RedisSentinelConfiguration}. + * * @author Christoph Strobl * @author Vikas Garg */ @@ -134,13 +135,14 @@ class RedisSentinelConfigurationUnitTests { assertThat(configuration.getSentinelPassword()).isEqualTo(RedisPassword.none()); } - @Test + @Test // GH-2218 void dataNodeUsernameDoesNotAffectSentinelUsername() { RedisSentinelConfiguration configuration = new RedisSentinelConfiguration("myMaster", Collections.singleton(HOST_AND_PORT_1)); + configuration.setUsername("data-admin"); configuration.setSentinelUsername("sentinel-admin"); - configuration.setUsername("app"); + assertThat(configuration.getDataNodeUsername()).isEqualTo("data-admin"); assertThat(configuration.getSentinelUsername()).isEqualTo("sentinel-admin"); } @@ -158,17 +160,19 @@ class RedisSentinelConfigurationUnitTests { assertThat(config.getSentinels()).hasSize(1).contains(new RedisNode("127.0.0.1", 123)); } - @Test + @Test // GH-2218 void readSentinelUsernameFromConfigProperty() { MockPropertySource propertySource = new MockPropertySource(); propertySource.setProperty("spring.redis.sentinel.master", "myMaster"); propertySource.setProperty("spring.redis.sentinel.nodes", HOST_AND_PORT_1); propertySource.setProperty("spring.redis.sentinel.username", "sentinel-admin"); + propertySource.setProperty("spring.redis.sentinel.password", "foo"); RedisSentinelConfiguration config = new RedisSentinelConfiguration(propertySource); - assertThat(config.getSentinelUsername()).isEqualTo(RedisPassword.of("sentinel-admin")); + assertThat(config.getSentinelUsername()).isEqualTo("sentinel-admin"); + assertThat(config.getSentinelPassword()).isEqualTo(RedisPassword.of("foo")); assertThat(config.getSentinels()).hasSize(1).contains(new RedisNode("127.0.0.1", 123)); } } diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryUnitTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryUnitTests.java index 3d5ec4f4e..569674221 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactoryUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -282,6 +282,20 @@ class JedisConnectionFactoryUnitTests { assertThat(connectionFactory.getClusterConfiguration()).isNull(); } + @Test // GH-2218 + void shouldConsiderSentinelAuthentication() { + + RedisSentinelConfiguration configuration = new RedisSentinelConfiguration(); + configuration.setSentinelUsername("sentinel"); + configuration.setSentinelPassword("the-password"); + connectionFactory = new JedisConnectionFactory(configuration, JedisClientConfiguration.defaultConfiguration()); + + JedisClientConfig clientConfig = connectionFactory.createSentinelClientConfig(configuration); + + assertThat(clientConfig.getUser()).isEqualTo("sentinel"); + assertThat(clientConfig.getPassword()).isEqualTo("the-password"); + } + @Test // DATAREDIS-574 void shouldReturnClusterConfiguration() { diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConvertersUnitTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConvertersUnitTests.java index ff835dd5c..0838b5cee 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConvertersUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConvertersUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2021 the original author or authors. + * Copyright 2014-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,8 @@ import org.springframework.data.redis.core.types.Expiration; import org.springframework.data.redis.core.types.RedisClientInfo; /** + * Unit tests for {@link LettuceConverters}. + * * @author Christoph Strobl * @author Vikas Garg */ @@ -256,54 +258,73 @@ class LettuceConvertersUnitTests { .isEqualTo(new GetExArgs().pxAt(10)); } - @Test + @Test // GH-2218 void sentinelConfigurationWithAuth() { - RedisPassword password = RedisPassword.of("88888888-8x8-getting-creative-now"); + + RedisPassword dataPassword = RedisPassword.of("data-secret"); + RedisPassword sentinelPassword = RedisPassword.of("sentinel-secret"); + RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration() .master(MASTER_NAME) .sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380); - sentinelConfiguration.setSentinelUsername("admin"); - sentinelConfiguration.setSentinelPassword(password); sentinelConfiguration.setUsername("app"); - sentinelConfiguration.setPassword(password); + sentinelConfiguration.setPassword(dataPassword); + + sentinelConfiguration.setSentinelUsername("admin"); + sentinelConfiguration.setSentinelPassword(sentinelPassword); + RedisURI redisURI = LettuceConverters.sentinelConfigurationToRedisURI(sentinelConfiguration); + assertThat(redisURI.getUsername()).isEqualTo("app"); + assertThat(redisURI.getPassword()).isEqualTo(dataPassword.get()); + redisURI.getSentinels().forEach(sentinel -> { assertThat(sentinel.getUsername()).isEqualTo("admin"); + assertThat(sentinel.getPassword()).isEqualTo(sentinelPassword.get()); }); } - @Test + @Test // GH-2218 void sentinelConfigurationSetSentinelPasswordIfUsernameNotPresent() { + RedisPassword password = RedisPassword.of("88888888-8x8-getting-creative-now"); + RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration() .master(MASTER_NAME) .sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380); - sentinelConfiguration.setSentinelPassword(password); sentinelConfiguration.setUsername("app"); sentinelConfiguration.setPassword(password); + sentinelConfiguration.setSentinelPassword(password); + RedisURI redisURI = LettuceConverters.sentinelConfigurationToRedisURI(sentinelConfiguration); + assertThat(redisURI.getUsername()).isEqualTo("app"); + redisURI.getSentinels().forEach(sentinel -> { assertThat(sentinel.getUsername()).isNull(); assertThat(sentinel.getPassword()).isNotNull(); }); } - @Test + @Test // GH-2218 void sentinelConfigurationShouldNotSetSentinelAuthIfUsernameIsPresentWithNoPassword() { + RedisPassword password = RedisPassword.of("88888888-8x8-getting-creative-now"); + RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration() .master(MASTER_NAME) .sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380); - sentinelConfiguration.setSentinelUsername("admin"); sentinelConfiguration.setUsername("app"); sentinelConfiguration.setPassword(password); + sentinelConfiguration.setSentinelUsername("admin"); + RedisURI redisURI = LettuceConverters.sentinelConfigurationToRedisURI(sentinelConfiguration); + assertThat(redisURI.getUsername()).isEqualTo("app"); + redisURI.getSentinels().forEach(sentinel -> { assertThat(sentinel.getUsername()).isNull(); assertThat(sentinel.getPassword()).isNull();