diff --git a/src/test/java/redis/clients/jedis/commands/jedis/JedisCommandsTestBase.java b/src/test/java/redis/clients/jedis/commands/jedis/JedisCommandsTestBase.java index d3951f0638..6613d6646e 100644 --- a/src/test/java/redis/clients/jedis/commands/jedis/JedisCommandsTestBase.java +++ b/src/test/java/redis/clients/jedis/commands/jedis/JedisCommandsTestBase.java @@ -15,11 +15,9 @@ public abstract class JedisCommandsTestBase { @Rule - public RedisVersionRule versionRule = new RedisVersionRule( - HostAndPorts.getRedisEndpoint("standalone0")); + public RedisVersionRule versionRule = new RedisVersionRule(endpoint); @Rule - public EnabledOnCommandRule enabledOnCommandRule = new EnabledOnCommandRule( - HostAndPorts.getRedisEndpoint("standalone0")); + public EnabledOnCommandRule enabledOnCommandRule = new EnabledOnCommandRule(endpoint); /** * Input data for parameterized tests. In principle all subclasses of this diff --git a/src/test/java/redis/clients/jedis/modules/ConsolidatedAccessControlListCommandsTest.java b/src/test/java/redis/clients/jedis/modules/ConsolidatedAccessControlListCommandsTest.java new file mode 100644 index 0000000000..2ebacda539 --- /dev/null +++ b/src/test/java/redis/clients/jedis/modules/ConsolidatedAccessControlListCommandsTest.java @@ -0,0 +1,185 @@ +package redis.clients.jedis.modules; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertThrows; + +import io.redis.test.annotations.SinceRedisVersion; +import java.util.Locale; +import java.util.function.Consumer; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.RedisProtocol; +import redis.clients.jedis.UnifiedJedis; +import redis.clients.jedis.bloom.RedisBloomProtocol.*; +import redis.clients.jedis.commands.ProtocolCommand; +import redis.clients.jedis.exceptions.JedisAccessControlException; +import redis.clients.jedis.json.JsonProtocol.JsonCommand; +import redis.clients.jedis.search.SearchProtocol.SearchCommand; +import redis.clients.jedis.search.schemafields.TextField; +import redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesCommand; +import redis.clients.jedis.util.SafeEncoder; + +@SinceRedisVersion(value = "7.9.0") +@RunWith(Parameterized.class) +public class ConsolidatedAccessControlListCommandsTest extends RedisModuleCommandsTestBase { + + public static final String USER_NAME = "moduser"; + public static final String USER_PASSWORD = "secret"; + + public ConsolidatedAccessControlListCommandsTest(RedisProtocol protocol) { + super(protocol); + } + + @After + @Override + public void tearDown() throws Exception { + try { + jedis.aclDelUser(USER_NAME); + } catch (Exception e) { } + super.tearDown(); + } + + @Test + public void listACLCategoriesTest() { + assertThat(jedis.aclCat(), + Matchers.hasItems("bloom", "cuckoo", "cms", "topk", "tdigest", + "search", "timeseries", "json")); + } + + @Test + public void grantBloomCommandTest() { + grantModuleCommandTest(BloomFilterCommand.RESERVE, client -> client.bfReserve("foo", 0.01, 10_000)); + } + + @Test + public void grantBloomCommandCatTest() { + grantModuleCommandCatTest("bloom", BloomFilterCommand.RESERVE, client -> client.bfReserve("foo", 0.01, 10_000)); + } + + @Test + public void grantCuckooCommandTest() { + grantModuleCommandTest(CuckooFilterCommand.RESERVE, client -> client.cfReserve("foo", 10_000)); + } + + @Test + public void grantCuckooCommandCatTest() { + grantModuleCommandCatTest("cuckoo", CuckooFilterCommand.RESERVE, client -> client.cfReserve("foo", 10_000)); + } + + @Test + public void grantCmsCommandTest() { + grantModuleCommandTest(CountMinSketchCommand.INITBYDIM, client -> client.cmsInitByDim("foo", 16, 4)); + } + + @Test + public void grantCmsCommandCatTest() { + grantModuleCommandCatTest("cms", CountMinSketchCommand.INITBYDIM, client -> client.cmsInitByDim("foo", 16, 4)); + } + + @Test + public void grantTopkCommandTest() { + grantModuleCommandTest(TopKCommand.RESERVE, client -> client.topkReserve("foo", 1000)); + } + + @Test + public void grantTopkCommandCatTest() { + grantModuleCommandCatTest("topk", TopKCommand.RESERVE, client -> client.topkReserve("foo", 1000)); + } + + @Test + public void grantTdigestCommandTest() { + grantModuleCommandTest(TDigestCommand.CREATE, client -> client.tdigestCreate("foo")); + } + + @Test + public void grantTdigestCommandCatTest() { + grantModuleCommandCatTest("tdigest", TDigestCommand.CREATE, client -> client.tdigestCreate("foo")); + } + + @Test + public void grantSearchCommandTest() { + grantModuleCommandTest(SearchCommand.CREATE, + client -> client.ftCreate("foo", TextField.of("bar"))); + } + + @Test + public void grantSearchCommandCatTest() { + grantModuleCommandCatTest("search", SearchCommand.CREATE, + client -> client.ftCreate("foo", TextField.of("bar"))); + } + + @Test + public void grantTimeseriesCommandTest() { + grantModuleCommandTest(TimeSeriesCommand.CREATE, client -> client.tsCreate("foo")); + } + + @Test + public void grantTimeseriesCommandCatTest() { + grantModuleCommandCatTest("timeseries", TimeSeriesCommand.CREATE, client -> client.tsCreate("foo")); + } + + @Test + public void grantJsonCommandTest() { + grantModuleCommandTest(JsonCommand.GET, client -> client.jsonGet("foo")); + } + + @Test + public void grantJsonCommandCatTest() { + grantModuleCommandCatTest("json", JsonCommand.GET, client -> client.jsonGet("foo")); + } + + private void grantModuleCommandTest(ProtocolCommand command, Consumer operation) { + // create and enable an user with permission to all keys but no commands + jedis.aclSetUser(USER_NAME, ">" + USER_PASSWORD, "on", "~*"); + + // client object with new user + try (UnifiedJedis client = new UnifiedJedis(hnp, + DefaultJedisClientConfig.builder().user(USER_NAME).password(USER_PASSWORD).build())) { + + // user can't execute commands + JedisAccessControlException noperm = assertThrows("Should throw a NOPERM exception", + JedisAccessControlException.class, () -> operation.accept(client)); + assertThat(noperm.getMessage(), + Matchers.oneOf(getNopermErrorMessage(false, command), getNopermErrorMessage(true, command))); + + // permit user to commands + jedis.aclSetUser(USER_NAME, "+" + SafeEncoder.encode(command.getRaw())); + + // user can now execute commands + operation.accept(client); + } + } + + private void grantModuleCommandCatTest(String category, ProtocolCommand command, Consumer operation) { + // create and enable an user with permission to all keys but no commands + jedis.aclSetUser(USER_NAME, ">" + USER_PASSWORD, "on", "~*"); + + // client object with new user + try (UnifiedJedis client = new UnifiedJedis(hnp, + DefaultJedisClientConfig.builder().user(USER_NAME).password(USER_PASSWORD).build())) { + + // user can't execute category commands + JedisAccessControlException noperm = assertThrows("Should throw a NOPERM exception", + JedisAccessControlException.class, () -> operation.accept(client)); + assertThat(noperm.getMessage(), + Matchers.oneOf(getNopermErrorMessage(false, command), getNopermErrorMessage(true, command))); + + // permit user to category commands + jedis.aclSetUser(USER_NAME, "+@" + category); + + // user can now execute category commands + operation.accept(client); + } + } + + private static String getNopermErrorMessage(boolean commandNameUpperCase, ProtocolCommand protocolCommand) { + String command = SafeEncoder.encode(protocolCommand.getRaw()); + return String.format("NOPERM User %s has no permissions to run the '%s' command", + USER_NAME, commandNameUpperCase ? command.toUpperCase(Locale.ENGLISH) : command.toLowerCase(Locale.ENGLISH)); + } +} diff --git a/src/test/java/redis/clients/jedis/modules/RedisModuleCommandsTestBase.java b/src/test/java/redis/clients/jedis/modules/RedisModuleCommandsTestBase.java index 7e0e085c17..cfe2f7910b 100644 --- a/src/test/java/redis/clients/jedis/modules/RedisModuleCommandsTestBase.java +++ b/src/test/java/redis/clients/jedis/modules/RedisModuleCommandsTestBase.java @@ -22,7 +22,7 @@ public abstract class RedisModuleCommandsTestBase { @Rule - public RedisVersionRule versionRule = new RedisVersionRule(hnp,DefaultJedisClientConfig.builder().build() ); + public RedisVersionRule versionRule = new RedisVersionRule(hnp, DefaultJedisClientConfig.builder().build()); /** * Input data for parameterized tests. In principle all subclasses of this @@ -39,6 +39,7 @@ public static Collection data() { protected static final HostAndPort hnp = HostAndPort.from(address); protected final RedisProtocol protocol; + protected Jedis jedis; protected UnifiedJedis client; /** @@ -65,15 +66,15 @@ public static void prepare() { @Before public void setUp() { - try (Jedis jedis = new Jedis(hnp)) { - jedis.flushAll(); - } + jedis = new Jedis(hnp, DefaultJedisClientConfig.builder().protocol(protocol).build()); + jedis.flushAll(); client = new UnifiedJedis(hnp, DefaultJedisClientConfig.builder().protocol(protocol).build()); } @After public void tearDown() throws Exception { client.close(); + jedis.close(); } }