Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test modules ACL support #4042

Merged
merged 7 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<UnifiedJedis> 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<UnifiedJedis> 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));
}
sazzad16 marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -39,6 +39,7 @@ public static Collection<Object[]> data() {
protected static final HostAndPort hnp = HostAndPort.from(address);
protected final RedisProtocol protocol;

protected Jedis jedis;
protected UnifiedJedis client;

/**
Expand All @@ -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();
}

}
Loading