diff --git a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java index c3a64234aa..9b81bb81f2 100644 --- a/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java +++ b/src/main/java/io/lettuce/core/AbstractRedisAsyncCommands.java @@ -96,10 +96,15 @@ public RedisFuture aclDeluser(String... usernames) { } @Override - public RedisFuture aclDryRun(String username, String command, V... args) { + public RedisFuture aclDryRun(String username, String command, String... args) { return dispatch(commandBuilder.aclDryRun(username, command, args)); } + @Override + public RedisFuture aclDryRun(String username, RedisCommand command) { + return dispatch(commandBuilder.aclDryRun(username, command)); + } + @Override public RedisFuture aclGenpass() { return dispatch(commandBuilder.aclGenpass()); diff --git a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java index a0d2fc8df4..64a101d4b7 100644 --- a/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java +++ b/src/main/java/io/lettuce/core/AbstractRedisReactiveCommands.java @@ -128,10 +128,15 @@ public Mono aclDeluser(String... usernames) { } @Override - public Mono aclDryRun(String username, String command, V... args) { + public Mono aclDryRun(String username, String command, String... args) { return createMono(() -> commandBuilder.aclDryRun(username, command, args)); } + @Override + public Mono aclDryRun(String username, RedisCommand command) { + return createMono(() -> commandBuilder.aclDryRun(username, command)); + } + @Override public Mono aclGenpass() { return createMono(commandBuilder::aclGenpass); diff --git a/src/main/java/io/lettuce/core/RedisCommandBuilder.java b/src/main/java/io/lettuce/core/RedisCommandBuilder.java index 5003250298..7e8fcd4e67 100644 --- a/src/main/java/io/lettuce/core/RedisCommandBuilder.java +++ b/src/main/java/io/lettuce/core/RedisCommandBuilder.java @@ -93,12 +93,26 @@ Command aclDeluser(String... usernames) { return createCommand(ACL, new IntegerOutput<>(codec), args); } - Command aclDryRun(String username, String command, V... commandArgs) { + Command aclDryRun(String username, String command, String... commandArgs) { LettuceAssert.notNull(username, "username " + MUST_NOT_BE_NULL); LettuceAssert.notNull(command, "command " + MUST_NOT_BE_NULL); CommandArgs args = new CommandArgs<>(codec); - args.add(DRYRUN).add(username).add(command).addValues(commandArgs); + args.add(DRYRUN).add(username).add(command); + + for (String commandArg : commandArgs) { + args.add(commandArg); + } + return createCommand(ACL, new StatusOutput<>(codec), args); + } + + Command aclDryRun(String username, RedisCommand command) { + LettuceAssert.notNull(username, "username " + MUST_NOT_BE_NULL); + LettuceAssert.notNull(command, "command " + MUST_NOT_BE_NULL); + + CommandArgs args = new CommandArgs<>(codec); + args.add(DRYRUN).add(username).add(command.getType()).addAll(command.getArgs()); + return createCommand(ACL, new StatusOutput<>(codec), args); } diff --git a/src/main/java/io/lettuce/core/api/async/RedisAclAsyncCommands.java b/src/main/java/io/lettuce/core/api/async/RedisAclAsyncCommands.java index 9e6eb2217b..7a124acddf 100644 --- a/src/main/java/io/lettuce/core/api/async/RedisAclAsyncCommands.java +++ b/src/main/java/io/lettuce/core/api/async/RedisAclAsyncCommands.java @@ -23,6 +23,7 @@ import io.lettuce.core.AclSetuserArgs; import io.lettuce.core.RedisFuture; import io.lettuce.core.protocol.CommandType; +import io.lettuce.core.protocol.RedisCommand; /** * Asynchronous executed commands for the ACL-API. @@ -65,7 +66,17 @@ public interface RedisAclAsyncCommands { * @return String reply: OK on success. * @since 6.2 */ - RedisFuture aclDryRun(String username, String command, V... args); + RedisFuture aclDryRun(String username, String command, String... args); + + /** + * Simulate the execution of a given command by a given user. + * + * @param username the specified username + * @param command the specified command to inspect + * @return String reply: OK on success. + * @since 6.2 + */ + RedisFuture aclDryRun(String username, RedisCommand command); /** * The command generates a password. diff --git a/src/main/java/io/lettuce/core/api/reactive/RedisAclReactiveCommands.java b/src/main/java/io/lettuce/core/api/reactive/RedisAclReactiveCommands.java index 92bfd56dc1..3e2142262d 100644 --- a/src/main/java/io/lettuce/core/api/reactive/RedisAclReactiveCommands.java +++ b/src/main/java/io/lettuce/core/api/reactive/RedisAclReactiveCommands.java @@ -24,6 +24,7 @@ import io.lettuce.core.AclCategory; import io.lettuce.core.AclSetuserArgs; import io.lettuce.core.protocol.CommandType; +import io.lettuce.core.protocol.RedisCommand; /** * Reactive executed commands for the ACL-API. @@ -66,7 +67,17 @@ public interface RedisAclReactiveCommands { * @return String reply: OK on success. * @since 6.2 */ - Mono aclDryRun(String username, String command, V... args); + Mono aclDryRun(String username, String command, String... args); + + /** + * Simulate the execution of a given command by a given user. + * + * @param username the specified username + * @param command the specified command to inspect + * @return String reply: OK on success. + * @since 6.2 + */ + Mono aclDryRun(String username, RedisCommand command); /** * The command generates a password. diff --git a/src/main/java/io/lettuce/core/api/sync/RedisAclCommands.java b/src/main/java/io/lettuce/core/api/sync/RedisAclCommands.java index 57d0b397b8..11cca44331 100644 --- a/src/main/java/io/lettuce/core/api/sync/RedisAclCommands.java +++ b/src/main/java/io/lettuce/core/api/sync/RedisAclCommands.java @@ -22,6 +22,7 @@ import io.lettuce.core.AclCategory; import io.lettuce.core.AclSetuserArgs; import io.lettuce.core.protocol.CommandType; +import io.lettuce.core.protocol.RedisCommand; /** * Synchronous executed commands for the ACL-API. @@ -64,7 +65,17 @@ public interface RedisAclCommands { * @return String reply: OK on success. * @since 6.2 */ - String aclDryRun(String username, String command, V... args); + String aclDryRun(String username, String command, String... args); + + /** + * Simulate the execution of a given command by a given user. + * + * @param username the specified username + * @param command the specified command to inspect + * @return String reply: OK on success. + * @since 6.2 + */ + String aclDryRun(String username, RedisCommand command); /** * The command generates a password. diff --git a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionAclAsyncCommands.java b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionAclAsyncCommands.java index b767daa8f2..43cb6e3bf6 100644 --- a/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionAclAsyncCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/async/NodeSelectionAclAsyncCommands.java @@ -22,6 +22,7 @@ import io.lettuce.core.AclCategory; import io.lettuce.core.AclSetuserArgs; import io.lettuce.core.protocol.CommandType; +import io.lettuce.core.protocol.RedisCommand; /** * Asynchronous executed commands on a node selection for the ACL-API. @@ -64,7 +65,17 @@ public interface NodeSelectionAclAsyncCommands { * @return String reply: OK on success. * @since 6.2 */ - AsyncExecutions aclDryRun(String username, String command, V... args); + AsyncExecutions aclDryRun(String username, String command, String... args); + + /** + * Simulate the execution of a given command by a given user. + * + * @param username the specified username + * @param command the specified command to inspect + * @return String reply: OK on success. + * @since 6.2 + */ + AsyncExecutions aclDryRun(String username, RedisCommand command); /** * The command generates a password. diff --git a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionAclCommands.java b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionAclCommands.java index 743409550c..f495dfbd29 100644 --- a/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionAclCommands.java +++ b/src/main/java/io/lettuce/core/cluster/api/sync/NodeSelectionAclCommands.java @@ -22,6 +22,7 @@ import io.lettuce.core.AclCategory; import io.lettuce.core.AclSetuserArgs; import io.lettuce.core.protocol.CommandType; +import io.lettuce.core.protocol.RedisCommand; /** * Synchronous executed commands on a node selection for the ACL-API. @@ -64,7 +65,17 @@ public interface NodeSelectionAclCommands { * @return String reply: OK on success. * @since 6.2 */ - Executions aclDryRun(String username, String command, V... args); + Executions aclDryRun(String username, String command, String... args); + + /** + * Simulate the execution of a given command by a given user. + * + * @param username the specified username + * @param command the specified command to inspect + * @return String reply: OK on success. + * @since 6.2 + */ + Executions aclDryRun(String username, RedisCommand command); /** * The command generates a password. diff --git a/src/main/java/io/lettuce/core/protocol/CommandArgs.java b/src/main/java/io/lettuce/core/protocol/CommandArgs.java index c264afc75d..b4ff2a304a 100644 --- a/src/main/java/io/lettuce/core/protocol/CommandArgs.java +++ b/src/main/java/io/lettuce/core/protocol/CommandArgs.java @@ -277,6 +277,20 @@ public CommandArgs add(ProtocolKeyword keyword) { return this; } + /** + * Add all arguments from {@link CommandArgs} + * + * @param args the args, must not be {@code null} + * @return the command args. + * @since 6.2 + */ + public CommandArgs addAll(CommandArgs args) { + + LettuceAssert.notNull(args, "CommandArgs must not be null"); + this.singularArguments.addAll(args.singularArguments); + return this; + } + @Override public String toString() { diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommands.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommands.kt index db6f18bcc7..9225bb24e0 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommands.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommands.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 the original author or authors. + * Copyright 2020-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. @@ -20,6 +20,7 @@ import io.lettuce.core.AclCategory import io.lettuce.core.AclSetuserArgs import io.lettuce.core.ExperimentalLettuceCoroutinesApi import io.lettuce.core.protocol.CommandType +import io.lettuce.core.protocol.RedisCommand import kotlinx.coroutines.flow.Flow /** @@ -64,7 +65,17 @@ interface RedisAclCoroutinesCommands { * @return String reply: OK on success. * @since 6.2 */ - suspend fun aclDryRun(username: String, command: String, vararg args: V): String? + suspend fun aclDryRun(username: String, command: String, vararg args: String): String? + + /** + * Simulate the execution of a given command by a given user. + * + * @param username the specified username + * @param command the specified command to inspect + * @return String reply: OK on success. + * @since 6.2 + */ + suspend fun aclDryRun(username: String, command: RedisCommand): String? /** * The command generates a password. diff --git a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommandsImpl.kt b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommandsImpl.kt index bbfee02089..77cc7d3967 100644 --- a/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommandsImpl.kt +++ b/src/main/kotlin/io/lettuce/core/api/coroutines/RedisAclCoroutinesCommandsImpl.kt @@ -21,6 +21,7 @@ import io.lettuce.core.AclSetuserArgs import io.lettuce.core.ExperimentalLettuceCoroutinesApi import io.lettuce.core.api.reactive.RedisAclReactiveCommands import io.lettuce.core.protocol.CommandType +import io.lettuce.core.protocol.RedisCommand import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.toList import kotlinx.coroutines.reactive.asFlow @@ -45,13 +46,24 @@ internal class RedisAclCoroutinesCommandsImpl(internal val ops override suspend fun aclCat(category: AclCategory): Set = ops.aclCat(category).awaitFirstOrElse { emptySet() } - override suspend fun aclDeluser(vararg usernames: String): Long? = ops.aclDeluser(*usernames).awaitFirstOrNull() + override suspend fun aclDeluser(vararg usernames: String): Long? = + ops.aclDeluser(*usernames).awaitFirstOrNull() - override suspend fun aclDryRun(username: String, command: String, vararg args: V): String? = ops.aclDryRun(username, command, *args).awaitFirstOrNull() + override suspend fun aclDryRun( + username: String, + command: String, + vararg args: String + ): String? = ops.aclDryRun(username, command, *args).awaitFirstOrNull() + + override suspend fun aclDryRun( + username: String, + command: RedisCommand + ): String? = ops.aclDryRun(username, command).awaitFirstOrNull() override suspend fun aclGenpass(): String? = ops.aclGenpass().awaitFirstOrNull() - override suspend fun aclGenpass(bits: Int): String? = ops.aclGenpass(bits).awaitFirstOrNull() + override suspend fun aclGenpass(bits: Int): String? = + ops.aclGenpass(bits).awaitFirstOrNull() override suspend fun aclGetuser(username: String): List = ops.aclGetuser(username).awaitFirst() diff --git a/src/main/templates/io/lettuce/core/api/RedisAclCommands.java b/src/main/templates/io/lettuce/core/api/RedisAclCommands.java index 32c3ce1336..5ac4876bf9 100644 --- a/src/main/templates/io/lettuce/core/api/RedisAclCommands.java +++ b/src/main/templates/io/lettuce/core/api/RedisAclCommands.java @@ -17,6 +17,7 @@ import io.lettuce.core.*; import io.lettuce.core.protocol.CommandType; +import io.lettuce.core.protocol.RedisCommand; import java.util.List; import java.util.Map; @@ -62,7 +63,17 @@ public interface RedisAclCommands { * @return String reply: OK on success. * @since 6.2 */ - String aclDryRun(String username, String command, V... args); + String aclDryRun(String username, String command, String... args); + + /** + * Simulate the execution of a given command by a given user. + * + * @param username the specified username + * @param command the specified command to inspect + * @return String reply: OK on success. + * @since 6.2 + */ + String aclDryRun(String username, RedisCommand command); /** * The command generates a password. diff --git a/src/test/java/io/lettuce/core/commands/AclCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/AclCommandIntegrationTests.java index 0ae81f015a..59c47ef578 100644 --- a/src/test/java/io/lettuce/core/commands/AclCommandIntegrationTests.java +++ b/src/test/java/io/lettuce/core/commands/AclCommandIntegrationTests.java @@ -29,6 +29,10 @@ import io.lettuce.core.RedisCommandExecutionException; import io.lettuce.core.TestSupport; import io.lettuce.core.api.sync.RedisCommands; +import io.lettuce.core.codec.StringCodec; +import io.lettuce.core.output.StatusOutput; +import io.lettuce.core.protocol.Command; +import io.lettuce.core.protocol.CommandArgs; import io.lettuce.core.protocol.CommandType; import io.lettuce.test.LettuceExtension; import io.lettuce.test.condition.EnabledOnCommand; @@ -70,11 +74,26 @@ void aclDeluser() { } @Test - @EnabledOnCommand("EVAL_RO") // Redis 7.0 + @EnabledOnCommand("EVAL_RO") // Redis 7.0 void aclDryRun() { assertThatThrownBy(() -> redis.aclDryRun("non-existing", "GET", "foo", "bar")) .isInstanceOf(RedisCommandExecutionException.class).hasMessageContaining("ERR User 'non-existing' not found"); assertThat(redis.aclDryRun("default", "GET", "foo", "bar")).isEqualTo("OK"); + + AclSetuserArgs args = AclSetuserArgs.Builder.on().addCommand(CommandType.GET).keyPattern("objects:*") + .addPassword("foobared"); + assertThat(redis.aclSetuser("foo", args)).isEqualTo("OK"); + + assertThat(redis.aclDryRun("foo", "GET", "objects:foo")).isEqualTo("OK"); + assertThat(redis.aclDryRun("foo", "GET", "baz")).contains("no permissions"); + + Command getFoo = new Command<>(CommandType.GET, new StatusOutput<>(StringCodec.UTF8), + new CommandArgs<>(StringCodec.UTF8).add("objects:foo")); + Command getBaz = new Command<>(CommandType.GET, new StatusOutput<>(StringCodec.UTF8), + new CommandArgs<>(StringCodec.UTF8).add("baz")); + + assertThat(redis.aclDryRun("foo", getFoo)).isEqualTo("OK"); + assertThat(redis.aclDryRun("foo", getBaz)).contains("no permissions"); } @Test @@ -90,7 +109,8 @@ void aclGetuser() { @Test void aclLoad() { - assertThatThrownBy(redis::aclLoad).isInstanceOf(RedisCommandExecutionException.class).hasMessageContaining("ERR This Redis instance is not configured to use an ACL file."); + assertThatThrownBy(redis::aclLoad).isInstanceOf(RedisCommandExecutionException.class) + .hasMessageContaining("ERR This Redis instance is not configured to use an ACL file."); } @Test @@ -111,13 +131,15 @@ void aclList() { @Test void aclSave() { - assertThatThrownBy(redis::aclSave).isInstanceOf(RedisCommandExecutionException.class).hasMessageContaining("ERR This Redis instance is not configured to use an ACL file."); + assertThatThrownBy(redis::aclSave).isInstanceOf(RedisCommandExecutionException.class) + .hasMessageContaining("ERR This Redis instance is not configured to use an ACL file."); } @Test void aclSetuser() { assertThat(redis.aclDeluser("foo")).isNotNull(); - AclSetuserArgs args = AclSetuserArgs.Builder.on().addCommand(CommandType.GET).keyPattern("objects:*").addPassword("foobared"); + AclSetuserArgs args = AclSetuserArgs.Builder.on().addCommand(CommandType.GET).keyPattern("objects:*") + .addPassword("foobared"); assertThat(redis.aclSetuser("foo", args)).isEqualTo("OK"); assertThat(redis.aclGetuser("foo")).contains("commands").contains("passwords").contains("keys"); assertThat(redis.aclDeluser("foo")).isNotNull(); @@ -141,4 +163,5 @@ void aclUsers() { void aclWhoami() { assertThat(redis.aclWhoami()).isEqualTo("default"); } + }