diff --git a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
index 94cd215614..46b80db0d4 100644
--- a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java
@@ -41,6 +41,7 @@
* @param Value type.
* @author Will Glozer
* @author Mark Paluch
+ * @author Tugdual Grall
*/
@SuppressWarnings("unchecked")
public abstract class AbstractRedisAsyncCommands implements RedisHashAsyncCommands, RedisKeyAsyncCommands,
diff --git a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
index 54dae38b2f..c341d96c94 100644
--- a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java
@@ -47,6 +47,7 @@
* @param Value type.
* @author Mark Paluch
* @author Nikolai Perevozchikov
+ * @author Tugdual Grall
* @since 4.0
*/
public abstract class AbstractRedisReactiveCommands implements RedisHashReactiveCommands,
diff --git a/src/main/java/io/lettuce/core/ConnectionState.java b/src/main/java/io/lettuce/core/ConnectionState.java
index 25bfc06d41..90df2aa46c 100644
--- a/src/main/java/io/lettuce/core/ConnectionState.java
+++ b/src/main/java/io/lettuce/core/ConnectionState.java
@@ -15,6 +15,8 @@
*/
package io.lettuce.core;
+import java.util.List;
+
import io.lettuce.core.protocol.ProtocolVersion;
/**
@@ -95,7 +97,27 @@ void setHandshakeResponse(HandshakeResponse handshakeResponse) {
this.handshakeResponse = handshakeResponse;
}
- void setUsername(String username) {
+ /**
+ * Sets username/password state based on the argument count from an {@code AUTH} command.
+ *
+ * @param args
+ */
+ protected void setUserNamePassword(List args) {
+
+ if (args.isEmpty()) {
+ return;
+ }
+
+ if (args.size() > 1) {
+ setUsername(new String(args.get(0)));
+ setPassword(args.get(1));
+ } else {
+ setUsername(null);
+ setPassword(args.get(0));
+ }
+ }
+
+ protected void setUsername(String username) {
this.username = username;
}
diff --git a/src/main/java/io/lettuce/core/RedisCommandBuilder.java b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
index 9055ccbcef..4c2753db29 100644
--- a/src/main/java/io/lettuce/core/RedisCommandBuilder.java
+++ b/src/main/java/io/lettuce/core/RedisCommandBuilder.java
@@ -35,6 +35,7 @@
* @param
* @author Mark Paluch
* @author Zhang Jessey
+ * @author Tugdual Grall
*/
@SuppressWarnings({ "unchecked", "varargs" })
class RedisCommandBuilder extends BaseRedisCommandBuilder {
diff --git a/src/main/java/io/lettuce/core/RedisHandshake.java b/src/main/java/io/lettuce/core/RedisHandshake.java
index 1b96a4eac3..b449355de7 100644
--- a/src/main/java/io/lettuce/core/RedisHandshake.java
+++ b/src/main/java/io/lettuce/core/RedisHandshake.java
@@ -34,6 +34,7 @@
* connection state restoration. This class is part of the internal API.
*
* @author Mark Paluch
+ * @author Tugdual Grall
* @since 6.0
*/
class RedisHandshake implements ConnectionInitializer {
@@ -151,8 +152,9 @@ private CompletableFuture initializeResp3(Channel channel) {
* @return
*/
private CompletableFuture> initiateHandshakeResp2(Channel channel) {
+
if (connectionState.hasUsername()) {
- return dispatch(channel, this.commandBuilder.auth(connectionState.getUsername() ,connectionState.getPassword()));
+ return dispatch(channel, this.commandBuilder.auth(connectionState.getUsername(), connectionState.getPassword()));
} else if (connectionState.hasPassword()) {
return dispatch(channel, this.commandBuilder.auth(connectionState.getPassword()));
} else if (this.pingOnConnect) {
diff --git a/src/main/java/io/lettuce/core/RedisURI.java b/src/main/java/io/lettuce/core/RedisURI.java
index 240244e48d..842206ffb6 100644
--- a/src/main/java/io/lettuce/core/RedisURI.java
+++ b/src/main/java/io/lettuce/core/RedisURI.java
@@ -64,19 +64,23 @@
*
* URI syntax
*
- * Redis Standalone redis{@code ://}[password@]host [{@code :}
- * port][{@code /}database][{@code ?} [timeout=timeout[d|h|m|s|ms|us|ns]] [
- * &database=database] [&clientName=clientName]]
+ * Redis Standalone redis{@code ://}[[username{@code :}]password@]host
+ * [{@code :} port][{@code /}database][{@code ?}
+ * [timeout=timeout[d|h|m|s|ms|us|ns]] [ &database=database] [&clientName=clientName]]
+ *
*
- * Redis Standalone (SSL) rediss{@code ://}[password@]host [{@code :}
+ * Redis Standalone (SSL)
+ * rediss{@code ://}[[username{@code :}]password@]host [{@code :}
* port][{@code /}database][{@code ?} [timeout=timeout[d|h|m|s|ms|us|ns]] [
* &database=database] [&clientName=clientName]]
*
- * Redis Standalone (Unix Domain Sockets) redis-socket{@code ://} [password@]path[
+ * Redis Standalone (Unix Domain Sockets) redis-socket{@code ://}
+ * [[username{@code :}]password@]path[
* {@code ?}[timeout=timeout[d|h|m|s|ms|us|ns]][&database=database]
* [&clientName=clientName]]
*
- * Redis Sentinel redis-sentinel{@code ://}[password@]host1 [{@code :}
+ * Redis Sentinel
+ * redis-sentinel{@code ://}[[username{@code :}]password@]host1 [{@code :}
* port1][, host2 [{@code :}port2]][, hostN [{@code :}portN]][{@code /}
* database][{@code ?} [timeout=timeout[d|h|m|s|ms|us|ns]] [
* &sentinelMasterId=sentinelMasterId] [&database=database] [&clientName=clientName]]
@@ -86,6 +90,9 @@
* Note: When using Redis Sentinel, the password from the URI applies to the data nodes only. Sentinel authentication must be
* configured for each {@link #getSentinels() sentinel node}.
*
+ *
+ * Note:Usernames are supported as of Redis 6.
+ *
*
*
* Schemes
diff --git a/src/main/java/io/lettuce/core/StatefulRedisConnectionImpl.java b/src/main/java/io/lettuce/core/StatefulRedisConnectionImpl.java
index 20a3dbcc2c..5f79e8d421 100644
--- a/src/main/java/io/lettuce/core/StatefulRedisConnectionImpl.java
+++ b/src/main/java/io/lettuce/core/StatefulRedisConnectionImpl.java
@@ -22,6 +22,7 @@
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.async.RedisAsyncCommands;
@@ -157,16 +158,14 @@ protected RedisCommand preProcessCommand(RedisCommand comm
local = attachOnComplete(local, status -> {
if ("OK".equals(status)) {
- char[] password = CommandArgsAccessor.getFirstCharArray(command.getArgs());
+ List args = CommandArgsAccessor.getCharArrayArguments(command.getArgs());
- if (password != null) {
- state.setPassword(password);
+ if (!args.isEmpty()) {
+ state.setUserNamePassword(args);
} else {
- String stringPassword = CommandArgsAccessor.getFirstString(command.getArgs());
- if (stringPassword != null) {
- state.setPassword(stringPassword.toCharArray());
- }
+ List strings = CommandArgsAccessor.getStringArguments(command.getArgs());
+ state.setUserNamePassword(strings.stream().map(String::toCharArray).collect(Collectors.toList()));
}
}
});
diff --git a/src/main/java/io/lettuce/core/api/async/RedisAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisAsyncCommands.java
index a2abc70caf..0ff16b257f 100644
--- a/src/main/java/io/lettuce/core/api/async/RedisAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/api/async/RedisAsyncCommands.java
@@ -42,11 +42,12 @@ public interface RedisAsyncCommands extends BaseRedisAsyncCommands,
RedisFuture auth(CharSequence password);
/**
- * Authenticate to the server.
+ * Authenticate to the server with username and password. Requires Redis 6 or newer.
*
* @param username the username
* @param password the password
* @return String simple-string-reply
+ * @since 6.0
*/
RedisFuture auth(String username, CharSequence password);
diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisReactiveCommands.java
index 1f31717b1a..0c999add37 100644
--- a/src/main/java/io/lettuce/core/api/reactive/RedisReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/api/reactive/RedisReactiveCommands.java
@@ -42,11 +42,12 @@ public interface RedisReactiveCommands extends BaseRedisReactiveCommands auth(CharSequence password);
/**
- * Authenticate to the server.
+ * Authenticate to the server with username and password. Requires Redis 6 or newer.
*
* @param username the username
* @param password the password
* @return String simple-string-reply
+ * @since 6.0
*/
Mono auth(String username, CharSequence password);
diff --git a/src/main/java/io/lettuce/core/api/sync/RedisCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisCommands.java
index 25885a8e17..5c5113efd7 100644
--- a/src/main/java/io/lettuce/core/api/sync/RedisCommands.java
+++ b/src/main/java/io/lettuce/core/api/sync/RedisCommands.java
@@ -33,21 +33,22 @@ public interface RedisCommands extends BaseRedisCommands, RedisClust
RedisStreamCommands, RedisStringCommands, RedisTransactionalCommands {
/**
- * Authenticate to the server with username and password.
+ * Authenticate to the server.
*
- * @param username the username
* @param password the password
* @return String simple-string-reply
*/
- String auth(String username, CharSequence password);
+ String auth(CharSequence password);
/**
- * Authenticate to the server.
+ * Authenticate to the server with username and password. Requires Redis 6 or newer.
*
+ * @param username the username
* @param password the password
* @return String simple-string-reply
+ * @since 6.0
*/
- String auth(CharSequence password);
+ String auth(String username, CharSequence password);
/**
* Change the selected database for the current Commands.
diff --git a/src/main/java/io/lettuce/core/cluster/StatefulRedisClusterConnectionImpl.java b/src/main/java/io/lettuce/core/cluster/StatefulRedisClusterConnectionImpl.java
index 8e4c60a3d8..dac67f5b9f 100644
--- a/src/main/java/io/lettuce/core/cluster/StatefulRedisClusterConnectionImpl.java
+++ b/src/main/java/io/lettuce/core/cluster/StatefulRedisClusterConnectionImpl.java
@@ -27,6 +27,7 @@
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import io.lettuce.core.*;
import io.lettuce.core.api.StatefulRedisConnection;
@@ -195,16 +196,15 @@ private RedisCommand preProcessCommand(RedisCommand comman
if (local.getType().name().equals(AUTH.name())) {
local = attachOnComplete(local, status -> {
if (status.equals("OK")) {
- char[] password = CommandArgsAccessor.getFirstCharArray(command.getArgs());
+ List args = CommandArgsAccessor.getCharArrayArguments(command.getArgs());
- if (password != null) {
- this.connectionState.setPassword(password);
+ if (!args.isEmpty()) {
+ this.connectionState.setUserNamePassword(args);
} else {
- String stringPassword = CommandArgsAccessor.getFirstString(command.getArgs());
- if (stringPassword != null) {
- this.connectionState.setPassword(stringPassword.toCharArray());
- }
+ List stringArgs = CommandArgsAccessor.getStringArguments(command.getArgs());
+ this.connectionState
+ .setUserNamePassword(stringArgs.stream().map(String::toCharArray).collect(Collectors.toList()));
}
}
});
@@ -264,8 +264,8 @@ ConnectionState getConnectionState() {
static class ClusterConnectionState extends ConnectionState {
@Override
- protected void setPassword(char[] password) {
- super.setPassword(password);
+ protected void setUserNamePassword(List args) {
+ super.setUserNamePassword(args);
}
@Override
diff --git a/src/main/java/io/lettuce/core/cluster/api/async/RedisClusterAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/RedisClusterAsyncCommands.java
index 49b4d39a55..9a99156f27 100644
--- a/src/main/java/io/lettuce/core/cluster/api/async/RedisClusterAsyncCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/async/RedisClusterAsyncCommands.java
@@ -54,11 +54,12 @@ public interface RedisClusterAsyncCommands extends BaseRedisAsyncCommands<
RedisFuture auth(CharSequence password);
/**
- * Authenticate to the server.
+ * Authenticate to the server with username and password. Requires Redis 6 or newer.
*
* @param username the username
* @param password the password
* @return String simple-string-reply
+ * @since 6.0
*/
RedisFuture auth(String username, CharSequence password);
diff --git a/src/main/java/io/lettuce/core/cluster/api/reactive/RedisClusterReactiveCommands.java b/src/main/java/io/lettuce/core/cluster/api/reactive/RedisClusterReactiveCommands.java
index db7bf8dd39..8ba83de011 100644
--- a/src/main/java/io/lettuce/core/cluster/api/reactive/RedisClusterReactiveCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/reactive/RedisClusterReactiveCommands.java
@@ -55,11 +55,12 @@ public interface RedisClusterReactiveCommands extends BaseRedisReactiveCom
Mono auth(CharSequence password);
/**
- * Authenticate to the server.
+ * Authenticate to the server with username and password. Requires Redis 6 or newer.
*
* @param username the username
* @param password the password
* @return String simple-string-reply
+ * @since 6.0
*/
Mono auth(String username, CharSequence password);
diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java
index 2994fd390a..68fe1209ec 100644
--- a/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java
+++ b/src/main/java/io/lettuce/core/cluster/api/sync/RedisClusterCommands.java
@@ -51,11 +51,12 @@ public interface RedisClusterCommands extends BaseRedisCommands, Red
String auth(CharSequence password);
/**
- * Authenticate to the server.
+ * Authenticate to the server with username and password. Requires Redis 6 or newer.
*
* @param username the username
* @param password the password
* @return String simple-string-reply
+ * @since 6.0
*/
String auth(String username, CharSequence password);
diff --git a/src/main/java/io/lettuce/core/protocol/CommandArgsAccessor.java b/src/main/java/io/lettuce/core/protocol/CommandArgsAccessor.java
index f510fc1e5a..f278eb6406 100644
--- a/src/main/java/io/lettuce/core/protocol/CommandArgsAccessor.java
+++ b/src/main/java/io/lettuce/core/protocol/CommandArgsAccessor.java
@@ -16,6 +16,8 @@
package io.lettuce.core.protocol;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
import io.lettuce.core.protocol.CommandArgs.CharArrayArgument;
import io.lettuce.core.protocol.CommandArgs.SingularArgument;
@@ -69,7 +71,7 @@ public static String getFirstString(CommandArgs commandArgs) {
}
/**
- * Get the first {@link char}-array argument.
+ * Get the first {@code char[]}-array argument.
*
* @param commandArgs must not be null.
* @return the first {@link String} argument or {@literal null}.
@@ -87,6 +89,52 @@ public static char[] getFirstCharArray(CommandArgs commandArgs) {
return null;
}
+ /**
+ * Get the all {@link String} arguments.
+ *
+ * @param commandArgs must not be null.
+ * @return the first {@link String} argument or {@literal null}.
+ * @since 6.0
+ */
+ public static List getStringArguments(CommandArgs commandArgs) {
+
+ List args = new ArrayList<>();
+
+ for (SingularArgument singularArgument : commandArgs.singularArguments) {
+
+ if (singularArgument instanceof StringArgument) {
+ args.add(((StringArgument) singularArgument).val);
+ }
+ }
+
+ return args;
+ }
+
+ /**
+ * Get the all {@code char[]} arguments.
+ *
+ * @param commandArgs must not be null.
+ * @return the first {@link String} argument or {@literal null}.
+ * @since 6.0
+ */
+ public static List getCharArrayArguments(CommandArgs commandArgs) {
+
+ List args = new ArrayList<>();
+
+ for (SingularArgument singularArgument : commandArgs.singularArguments) {
+
+ if (singularArgument instanceof CharArrayArgument) {
+ args.add(((CharArrayArgument) singularArgument).val);
+ }
+
+ if (singularArgument instanceof StringArgument) {
+ args.add(((StringArgument) singularArgument).val.toCharArray());
+ }
+ }
+
+ return args;
+ }
+
/**
* Get the first {@link Long integer} argument.
*
diff --git a/src/test/java/io/lettuce/core/ClientOptionsIntegrationTests.java b/src/test/java/io/lettuce/core/ClientOptionsIntegrationTests.java
index 45bf5fadae..17ba804ba4 100644
--- a/src/test/java/io/lettuce/core/ClientOptionsIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/ClientOptionsIntegrationTests.java
@@ -35,6 +35,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import io.lettuce.test.condition.EnabledOnCommand;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import io.lettuce.core.api.StatefulRedisConnection;
@@ -131,6 +132,7 @@ void testHitRequestQueueLimitReconnectWithAuthCommand() {
}
@Test
+ @EnabledOnCommand("ACL")
void testHitRequestQueueLimitReconnectWithAuthUsernamePasswordCommand() {
WithPassword.run(client, () -> {
diff --git a/src/test/java/io/lettuce/core/ConnectionCommandIntegrationTests.java b/src/test/java/io/lettuce/core/ConnectionCommandIntegrationTests.java
index 27ae88be39..c56bb5d93b 100644
--- a/src/test/java/io/lettuce/core/ConnectionCommandIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/ConnectionCommandIntegrationTests.java
@@ -25,7 +25,6 @@
import javax.inject.Inject;
-import io.lettuce.core.protocol.RedisCommand;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -35,10 +34,12 @@
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.protocol.ProtocolVersion;
import io.lettuce.test.*;
+import io.lettuce.test.condition.EnabledOnCommand;
/**
* @author Will Glozer
* @author Mark Paluch
+ * @author Tugdual Grall
*/
@ExtendWith(LettuceExtension.class)
class ConnectionCommandIntegrationTests extends TestSupport {
@@ -64,25 +65,44 @@ void auth() {
client.setOptions(
ClientOptions.builder().pingBeforeActivateConnection(false).protocolVersion(ProtocolVersion.RESP2).build());
RedisCommands connection = client.connect().sync();
- try {
- connection.ping();
- fail("Server doesn't require authentication");
- } catch (RedisException e) {
- assertThat(e.getMessage()).isEqualTo("NOAUTH Authentication required.");
- assertThat(connection.auth(passwd)).isEqualTo("OK");
- assertThat(connection.set(key, value)).isEqualTo("OK");
-
- // Aut with the same user & password (default)
- assertThat(connection.auth(username, passwd)).isEqualTo("OK");
- assertThat(connection.set(key, value)).isEqualTo("OK");
-
- // Switch to another user
- assertThat(connection.auth(sampleUsername, samplePasswd)).isEqualTo("OK");
- assertThat(connection.set("cached:demo", value)).isEqualTo("OK");
- assertThatThrownBy(() -> connection.get(key)).isInstanceOf(RedisCommandExecutionException.class);
- assertThat(connection.del("cached:demo")).isEqualTo(1);
- }
+ assertThatThrownBy(connection::ping).isInstanceOf(RedisException.class)
+ .hasMessageContaining("NOAUTH Authentication required");
+
+ assertThat(connection.auth(passwd)).isEqualTo("OK");
+ assertThat(connection.set(key, value)).isEqualTo("OK");
+
+ RedisURI redisURI = RedisURI.Builder.redis(host, port).withDatabase(2).withPassword(passwd).build();
+ RedisCommands authConnection = client.connect(redisURI).sync();
+ authConnection.ping();
+ authConnection.getStatefulConnection().close();
+ });
+ }
+
+ @Test
+ @EnabledOnCommand("ACL")
+ void authWithUsername() {
+
+ WithPassword.run(client, () -> {
+ client.setOptions(
+ ClientOptions.builder().pingBeforeActivateConnection(false).protocolVersion(ProtocolVersion.RESP2).build());
+ RedisCommands connection = client.connect().sync();
+
+ assertThatThrownBy(connection::ping).isInstanceOf(RedisException.class)
+ .hasMessageContaining("NOAUTH Authentication required");
+
+ assertThat(connection.auth(passwd)).isEqualTo("OK");
+ assertThat(connection.set(key, value)).isEqualTo("OK");
+
+ // Aut with the same user & password (default)
+ assertThat(connection.auth(username, passwd)).isEqualTo("OK");
+ assertThat(connection.set(key, value)).isEqualTo("OK");
+
+ // Switch to another user
+ assertThat(connection.auth(aclUsername, aclPasswd)).isEqualTo("OK");
+ assertThat(connection.set("cached:demo", value)).isEqualTo("OK");
+ assertThatThrownBy(() -> connection.get(key)).isInstanceOf(RedisCommandExecutionException.class);
+ assertThat(connection.del("cached:demo")).isEqualTo(1);
RedisURI redisURI = RedisURI.Builder.redis(host, port).withDatabase(2).withPassword(passwd).build();
RedisCommands authConnection = client.connect(redisURI).sync();
@@ -92,12 +112,15 @@ void auth() {
}
@Test
+ @EnabledOnCommand("ACL")
void resp2HandShakeWithUsernamePassword() {
- RedisURI redisURI = RedisURI.Builder.redis(host, port).withAuthentication(username,passwd).build();
+
+ RedisURI redisURI = RedisURI.Builder.redis(host, port).withAuthentication(username, passwd).build();
RedisClient clientResp2 = RedisClient.create(redisURI);
clientResp2.setOptions(
ClientOptions.builder().pingBeforeActivateConnection(false).protocolVersion(ProtocolVersion.RESP2).build());
RedisCommands connTestResp2 = null;
+
try {
connTestResp2 = clientResp2.connect().sync();
assertThat(redis.ping()).isEqualTo("PONG");
@@ -131,17 +154,36 @@ void select() {
@Test
void authNull() {
assertThatThrownBy(() -> redis.auth(null)).isInstanceOf(IllegalArgumentException.class);
- assertThatThrownBy(() -> redis.auth(null,"x")).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> redis.auth(null, "x")).isInstanceOf(IllegalArgumentException.class);
}
@Test
void authEmpty() {
assertThatThrownBy(() -> redis.auth("")).isInstanceOf(IllegalArgumentException.class);
- assertThatThrownBy(() -> redis.auth("","x")).isInstanceOf(IllegalArgumentException.class);
+ assertThatThrownBy(() -> redis.auth("", "x")).isInstanceOf(IllegalArgumentException.class);
}
@Test
void authReconnect() {
+ WithPassword.run(client, () -> {
+
+ client.setOptions(
+ ClientOptions.builder().protocolVersion(ProtocolVersion.RESP2).pingBeforeActivateConnection(false).build());
+ RedisCommands connection = client.connect().sync();
+ assertThat(connection.auth(passwd)).isEqualTo("OK");
+ assertThat(connection.set(key, value)).isEqualTo("OK");
+ connection.quit();
+
+ Delay.delay(Duration.ofMillis(100));
+ assertThat(connection.get(key)).isEqualTo(value);
+
+ connection.getStatefulConnection().close();
+ });
+ }
+
+ @Test
+ @EnabledOnCommand("ACL")
+ void authReconnectRedis6() {
WithPassword.run(client, () -> {
client.setOptions(
@@ -201,22 +243,30 @@ void authInvalidPassword() {
}
@Test
+ @EnabledOnCommand("ACL")
void authInvalidUsernamePassword() {
WithPassword.run(client, () -> {
client.setOptions(
ClientOptions.builder().protocolVersion(ProtocolVersion.RESP2).pingBeforeActivateConnection(false).build());
RedisCommands connection = client.connect().sync();
+
assertThat(connection.auth(username, passwd)).isEqualTo("OK");
- assertThatThrownBy(() -> connection.auth( username,"invalid")).hasMessage("WRONGPASS invalid username-password pair");
- assertThat(connection.auth(sampleUsername, samplePasswd)).isEqualTo("OK");
- assertThatThrownBy(() -> connection.auth( sampleUsername,"invalid")).hasMessage("WRONGPASS invalid username-password pair");
- connection.getStatefulConnection().close();
+ assertThatThrownBy(() -> connection.auth(username, "invalid"))
+ .hasMessage("WRONGPASS invalid username-password pair");
+
+ assertThat(connection.auth(aclUsername, aclPasswd)).isEqualTo("OK");
+
+ assertThatThrownBy(() -> connection.auth(aclUsername, "invalid"))
+ .hasMessage("WRONGPASS invalid username-password pair");
+
+ connection.getStatefulConnection().close();
});
}
@Test
+ @EnabledOnCommand("ACL")
void authInvalidDefaultPasswordNoACL() {
RedisAsyncCommands async = client.connect().async();
// When the database is not secured the AUTH default invalid command returns OK
diff --git a/src/test/java/io/lettuce/core/ReactiveConnectionIntegrationTests.java b/src/test/java/io/lettuce/core/ReactiveConnectionIntegrationTests.java
index 5266151620..b7fae489c0 100644
--- a/src/test/java/io/lettuce/core/ReactiveConnectionIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/ReactiveConnectionIntegrationTests.java
@@ -28,7 +28,6 @@
import javax.enterprise.inject.New;
import javax.inject.Inject;
-import io.lettuce.test.WithPassword;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
@@ -45,10 +44,13 @@
import io.lettuce.test.Delay;
import io.lettuce.test.LettuceExtension;
import io.lettuce.test.Wait;
+import io.lettuce.test.WithPassword;
+import io.lettuce.test.condition.EnabledOnCommand;
/**
* @author Mark Paluch
* @author Nikolai Perevozchikov
+ * @author Tugdual Grall
*/
@ExtendWith(LettuceExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -203,17 +205,31 @@ void transactional(RedisClient client) throws Exception {
@Test
void auth() {
- StepVerifier.create(reactive.auth("error")).expectError().verify();
- StepVerifier.create(reactive.auth(username, "error")).expectNext("OK").verifyComplete();
+ WithPassword.enableAuthentication(this.connection.sync());
+
+ try {
+ StepVerifier.create(reactive.auth("error")).expectError().verify();
+ } finally {
+ WithPassword.disableAuthentication(this.connection.sync());
+ }
+ }
+
+ @Test
+ @EnabledOnCommand("ACL")
+ void authWithUsername() {
- WithPassword.enableAuthentication( this.connection.sync());
+ try {
- StepVerifier.create(reactive.auth(username, "error")).expectError().verify();
+ StepVerifier.create(reactive.auth(username, "error")).expectNext("OK").verifyComplete();
- StepVerifier.create(reactive.auth(sampleUsername, samplePasswd)).expectNext("OK").verifyComplete();
- StepVerifier.create(reactive.auth(sampleUsername, "error")).expectError().verify();
+ WithPassword.enableAuthentication(this.connection.sync());
- WithPassword.disableAuthentication( this.connection.sync());
+ StepVerifier.create(reactive.auth(username, "error")).expectError().verify();
+ StepVerifier.create(reactive.auth(aclUsername, aclPasswd)).expectNext("OK").verifyComplete();
+ StepVerifier.create(reactive.auth(aclUsername, "error")).expectError().verify();
+ } finally {
+ WithPassword.disableAuthentication(this.connection.sync());
+ }
}
@Test
@@ -239,14 +255,11 @@ void subscribeWithDisconnectedClient(RedisClient client) {
connection.async().quit();
Wait.untilTrue(() -> !connection.isOpen()).waitOrTimeout();
- StepVerifier
- .create(connection.reactive().ping())
- .consumeErrorWith(
- throwable -> {
- assertThat(throwable).isInstanceOf(RedisException.class).hasMessageContaining(
- "not connected. Commands are rejected");
+ StepVerifier.create(connection.reactive().ping()).consumeErrorWith(throwable -> {
+ assertThat(throwable).isInstanceOf(RedisException.class)
+ .hasMessageContaining("not connected. Commands are rejected");
- }).verify();
+ }).verify();
connection.close();
}
diff --git a/src/test/java/io/lettuce/core/TestSupport.java b/src/test/java/io/lettuce/core/TestSupport.java
index 410ffcda8a..61cdb7b1c5 100644
--- a/src/test/java/io/lettuce/core/TestSupport.java
+++ b/src/test/java/io/lettuce/core/TestSupport.java
@@ -24,6 +24,7 @@
/**
* @author Mark Paluch
+ * @author Tugdual Grall
*/
public abstract class TestSupport {
@@ -32,8 +33,8 @@ public abstract class TestSupport {
public static final String username = TestSettings.username();
public static final String passwd = TestSettings.password();
- public static final String sampleUsername = TestSettings.sampleUsername();
- public static final String samplePasswd = TestSettings.samplePassword();
+ public static final String aclUsername = TestSettings.aclUsername();
+ public static final String aclPasswd = TestSettings.aclPassword();
public static final String key = "key";
public static final String value = "value";
diff --git a/src/test/java/io/lettuce/core/pubsub/PubSubCommandTest.java b/src/test/java/io/lettuce/core/pubsub/PubSubCommandTest.java
index 54f6442838..bc83302d3e 100644
--- a/src/test/java/io/lettuce/core/pubsub/PubSubCommandTest.java
+++ b/src/test/java/io/lettuce/core/pubsub/PubSubCommandTest.java
@@ -42,12 +42,14 @@
import io.lettuce.test.TestFutures;
import io.lettuce.test.Wait;
import io.lettuce.test.WithPassword;
+import io.lettuce.test.condition.EnabledOnCommand;
import io.lettuce.test.resource.FastShutdown;
import io.lettuce.test.resource.TestClientResources;
/**
* @author Will Glozer
* @author Mark Paluch
+ * @author Tugdual Grall
*/
class PubSubCommandTest extends AbstractRedisClientTest implements RedisPubSubListener {
@@ -98,6 +100,7 @@ void auth() {
}
@Test
+ @EnabledOnCommand("ACL")
void authWithUsername() {
WithPassword.run(client, () -> {
@@ -105,7 +108,7 @@ void authWithUsername() {
ClientOptions.builder().protocolVersion(ProtocolVersion.RESP2).pingBeforeActivateConnection(false).build());
RedisPubSubAsyncCommands connection = client.connectPubSub().async();
connection.getStatefulConnection().addListener(PubSubCommandTest.this);
- connection.auth(username,passwd);
+ connection.auth(username, passwd);
connection.subscribe(channel);
assertThat(channels.take()).isEqualTo(channel);
@@ -123,13 +126,42 @@ void authWithReconnect() {
RedisPubSubAsyncCommands connection = client.connectPubSub().async();
connection.getStatefulConnection().addListener(PubSubCommandTest.this);
connection.auth(passwd);
+
connection.clientSetname("authWithReconnect");
- connection.subscribe(channel);
+ connection.subscribe(channel).get();
assertThat(channels.take()).isEqualTo(channel);
- redis.auth(username, passwd);
long id = findNamedClient("authWithReconnect");
+ redis.auth(passwd);
+ redis.clientKill(KillArgs.Builder.id(id));
+
+ Delay.delay(Duration.ofMillis(100));
+ Wait.untilTrue(connection::isOpen).waitOrTimeout();
+
+ assertThat(channels.take()).isEqualTo(channel);
+ });
+ }
+
+ @Test
+ @EnabledOnCommand("ACL")
+ void authWithUsernameAndReconnect() {
+
+ WithPassword.run(client, () -> {
+
+ client.setOptions(
+ ClientOptions.builder().protocolVersion(ProtocolVersion.RESP2).pingBeforeActivateConnection(false).build());
+
+ RedisPubSubAsyncCommands connection = client.connectPubSub().async();
+ connection.getStatefulConnection().addListener(PubSubCommandTest.this);
+ connection.auth(username, passwd);
+ connection.clientSetname("authWithReconnect");
+ connection.subscribe(channel).get();
+
+ assertThat(channels.take()).isEqualTo(channel);
+
+ long id = findNamedClient("authWithReconnect");
+ redis.auth(username, passwd);
redis.clientKill(KillArgs.Builder.id(id));
Delay.delay(Duration.ofMillis(100));
diff --git a/src/test/java/io/lettuce/examples/ConnectToRedis.java b/src/test/java/io/lettuce/examples/ConnectToRedis.java
index 8cd6e73f73..744fcaf466 100644
--- a/src/test/java/io/lettuce/examples/ConnectToRedis.java
+++ b/src/test/java/io/lettuce/examples/ConnectToRedis.java
@@ -20,6 +20,7 @@
/**
* @author Mark Paluch
+ * @author Tugdual Grall
*/
public class ConnectToRedis {
diff --git a/src/test/java/io/lettuce/examples/ConnectToRedisCluster.java b/src/test/java/io/lettuce/examples/ConnectToRedisCluster.java
index 59e7ce93a2..41cec7d01d 100644
--- a/src/test/java/io/lettuce/examples/ConnectToRedisCluster.java
+++ b/src/test/java/io/lettuce/examples/ConnectToRedisCluster.java
@@ -20,6 +20,7 @@
/**
* @author Mark Paluch
+ * @author Tugdual Grall
*/
public class ConnectToRedisCluster {
diff --git a/src/test/java/io/lettuce/test/WithPassword.java b/src/test/java/io/lettuce/test/WithPassword.java
index 94e70bac11..d51b986735 100644
--- a/src/test/java/io/lettuce/test/WithPassword.java
+++ b/src/test/java/io/lettuce/test/WithPassword.java
@@ -17,18 +17,11 @@
import java.lang.reflect.UndeclaredThrowableException;
import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
-import io.lettuce.core.codec.StringCodec;
-import io.lettuce.core.output.StatusOutput;
-import io.lettuce.core.protocol.AsyncCommand;
import io.lettuce.core.protocol.Command;
-import io.lettuce.core.protocol.CommandType;
-import io.lettuce.core.protocol.RedisCommand;
import io.lettuce.test.condition.RedisConditions;
import io.lettuce.test.settings.TestSettings;
@@ -36,12 +29,10 @@
* Utility to run a {@link ThrowingCallable callback function} while Redis is configured with a password.
*
* @author Mark Paluch
+ * @author Tugdual Grall
*/
public class WithPassword {
- private boolean hasACLCommand = false;
-
-
/**
* Run a {@link ThrowingCallable callback function} while Redis is configured with a password.
*
@@ -83,7 +74,7 @@ public static void enableAuthentication(RedisCommands commands)
// If ACL is supported let's create a test user
if (conditions.hasCommand("ACL")) {
Command> command = CliParser.parse(
- "ACL SETUSER "+ TestSettings.sampleUsername() +" on >"+ TestSettings.samplePassword() +" ~cached:* +@all");
+ "ACL SETUSER " + TestSettings.aclUsername() + " on >" + TestSettings.aclPassword() + " ~cached:* +@all");
commands.dispatch(command.getType(), command.getOutput(), command.getArgs());
}
}
@@ -95,12 +86,13 @@ public static void enableAuthentication(RedisCommands commands)
*/
public static void disableAuthentication(RedisCommands commands) {
- RedisConditions conditions = RedisConditions.of(commands);
commands.auth(TestSettings.password()); // reauthenticate as default user before disabling it
+
+ RedisConditions conditions = RedisConditions.of(commands);
commands.configSet("requirepass", "");
if (conditions.hasCommand("ACL")) {
- Command> command = CliParser.parse("ACL DELUSER "+ TestSettings.sampleUsername());
+ Command> command = CliParser.parse("ACL DELUSER " + TestSettings.aclUsername());
commands.dispatch(command.getType(), command.getOutput(), command.getArgs());
command = CliParser.parse("acl setuser default nopass");
diff --git a/src/test/java/io/lettuce/test/settings/TestSettings.java b/src/test/java/io/lettuce/test/settings/TestSettings.java
index 33f81123a9..e240a97528 100644
--- a/src/test/java/io/lettuce/test/settings/TestSettings.java
+++ b/src/test/java/io/lettuce/test/settings/TestSettings.java
@@ -23,10 +23,11 @@
* This class provides settings used while testing. You can override these using system properties.
*
* @author Mark Paluch
+ * @author Tugdual Grall
*/
public class TestSettings {
- private TestSettings() {
+ private TestSettings() {
}
/**
@@ -75,7 +76,6 @@ public static String hostAddr() {
}
}
-
/**
*
* @return default username of your redis instance.
@@ -96,19 +96,19 @@ public static String password() {
/**
*
* @return password of a second user your redis instance. Defaults to {@literal lettuceTest}. Can be overridden with
- * {@code --Dsample.username=SampleUsername}
+ * {@code -Dacl.username=SampleUsername}
*/
- public static String sampleUsername() {
- return System.getProperty("sample.username", "lettuceTest");
+ public static String aclUsername() {
+ return System.getProperty("acl.username", "lettuceTest");
}
/**
*
- * @return password of a second user of your redis instance. Defaults to {@literal lettuceTestPasswd}. Can be overridden with
- * {@code -Dsample.password=SamplePassword}
+ * @return password of a second user of your redis instance. Defaults to {@literal lettuceTestPasswd}. Can be overridden
+ * with {@code -Dacl.password=SamplePassword}
*/
- public static String samplePassword() {
- return System.getProperty("sample.password", "lettuceTestPasswd");
+ public static String aclPassword() {
+ return System.getProperty("acl.password", "lettuceTestPasswd");
}
/**
@@ -116,7 +116,7 @@ public static String samplePassword() {
* @return port of your redis instance. Defaults to {@literal 6479}. Can be overriden with {@code -Dport=1234}
*/
public static int port() {
- return Integer.valueOf(System.getProperty("port", "6479"));
+ return Integer.parseInt(System.getProperty("port", "6479"));
}
/**
@@ -124,7 +124,7 @@ public static int port() {
* @return sslport of your redis instance. Defaults to {@literal 6443}. Can be overriden with {@code -Dsslport=1234}
*/
public static int sslPort() {
- return Integer.valueOf(System.getProperty("sslport", "6443"));
+ return Integer.parseInt(System.getProperty("sslport", "6443"));
}
/**