Skip to content

Commit 5e1b5c8

Browse files
authored
Test modules ACL support (#4042)
* Test HASH module ACL support * Add version rule * Test modules acl categories * Test according to design doc with Redis 8.0-M03 * Based on RedisModuleCommandsTestBase
1 parent 4b10f2e commit 5e1b5c8

File tree

3 files changed

+192
-8
lines changed

3 files changed

+192
-8
lines changed

src/test/java/redis/clients/jedis/commands/jedis/JedisCommandsTestBase.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@
1515
public abstract class JedisCommandsTestBase {
1616

1717
@Rule
18-
public RedisVersionRule versionRule = new RedisVersionRule(
19-
HostAndPorts.getRedisEndpoint("standalone0"));
18+
public RedisVersionRule versionRule = new RedisVersionRule(endpoint);
2019
@Rule
21-
public EnabledOnCommandRule enabledOnCommandRule = new EnabledOnCommandRule(
22-
HostAndPorts.getRedisEndpoint("standalone0"));
20+
public EnabledOnCommandRule enabledOnCommandRule = new EnabledOnCommandRule(endpoint);
2321

2422
/**
2523
* Input data for parameterized tests. In principle all subclasses of this
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package redis.clients.jedis.modules;
2+
3+
import static org.hamcrest.MatcherAssert.assertThat;
4+
import static org.junit.Assert.assertThrows;
5+
6+
import io.redis.test.annotations.SinceRedisVersion;
7+
import java.util.Locale;
8+
import java.util.function.Consumer;
9+
import org.hamcrest.Matchers;
10+
import org.junit.After;
11+
import org.junit.Test;
12+
import org.junit.runner.RunWith;
13+
import org.junit.runners.Parameterized;
14+
15+
import redis.clients.jedis.DefaultJedisClientConfig;
16+
import redis.clients.jedis.RedisProtocol;
17+
import redis.clients.jedis.UnifiedJedis;
18+
import redis.clients.jedis.bloom.RedisBloomProtocol.*;
19+
import redis.clients.jedis.commands.ProtocolCommand;
20+
import redis.clients.jedis.exceptions.JedisAccessControlException;
21+
import redis.clients.jedis.json.JsonProtocol.JsonCommand;
22+
import redis.clients.jedis.search.SearchProtocol.SearchCommand;
23+
import redis.clients.jedis.search.schemafields.TextField;
24+
import redis.clients.jedis.timeseries.TimeSeriesProtocol.TimeSeriesCommand;
25+
import redis.clients.jedis.util.SafeEncoder;
26+
27+
@SinceRedisVersion(value = "7.9.0")
28+
@RunWith(Parameterized.class)
29+
public class ConsolidatedAccessControlListCommandsTest extends RedisModuleCommandsTestBase {
30+
31+
public static final String USER_NAME = "moduser";
32+
public static final String USER_PASSWORD = "secret";
33+
34+
public ConsolidatedAccessControlListCommandsTest(RedisProtocol protocol) {
35+
super(protocol);
36+
}
37+
38+
@After
39+
@Override
40+
public void tearDown() throws Exception {
41+
try {
42+
jedis.aclDelUser(USER_NAME);
43+
} catch (Exception e) { }
44+
super.tearDown();
45+
}
46+
47+
@Test
48+
public void listACLCategoriesTest() {
49+
assertThat(jedis.aclCat(),
50+
Matchers.hasItems("bloom", "cuckoo", "cms", "topk", "tdigest",
51+
"search", "timeseries", "json"));
52+
}
53+
54+
@Test
55+
public void grantBloomCommandTest() {
56+
grantModuleCommandTest(BloomFilterCommand.RESERVE, client -> client.bfReserve("foo", 0.01, 10_000));
57+
}
58+
59+
@Test
60+
public void grantBloomCommandCatTest() {
61+
grantModuleCommandCatTest("bloom", BloomFilterCommand.RESERVE, client -> client.bfReserve("foo", 0.01, 10_000));
62+
}
63+
64+
@Test
65+
public void grantCuckooCommandTest() {
66+
grantModuleCommandTest(CuckooFilterCommand.RESERVE, client -> client.cfReserve("foo", 10_000));
67+
}
68+
69+
@Test
70+
public void grantCuckooCommandCatTest() {
71+
grantModuleCommandCatTest("cuckoo", CuckooFilterCommand.RESERVE, client -> client.cfReserve("foo", 10_000));
72+
}
73+
74+
@Test
75+
public void grantCmsCommandTest() {
76+
grantModuleCommandTest(CountMinSketchCommand.INITBYDIM, client -> client.cmsInitByDim("foo", 16, 4));
77+
}
78+
79+
@Test
80+
public void grantCmsCommandCatTest() {
81+
grantModuleCommandCatTest("cms", CountMinSketchCommand.INITBYDIM, client -> client.cmsInitByDim("foo", 16, 4));
82+
}
83+
84+
@Test
85+
public void grantTopkCommandTest() {
86+
grantModuleCommandTest(TopKCommand.RESERVE, client -> client.topkReserve("foo", 1000));
87+
}
88+
89+
@Test
90+
public void grantTopkCommandCatTest() {
91+
grantModuleCommandCatTest("topk", TopKCommand.RESERVE, client -> client.topkReserve("foo", 1000));
92+
}
93+
94+
@Test
95+
public void grantTdigestCommandTest() {
96+
grantModuleCommandTest(TDigestCommand.CREATE, client -> client.tdigestCreate("foo"));
97+
}
98+
99+
@Test
100+
public void grantTdigestCommandCatTest() {
101+
grantModuleCommandCatTest("tdigest", TDigestCommand.CREATE, client -> client.tdigestCreate("foo"));
102+
}
103+
104+
@Test
105+
public void grantSearchCommandTest() {
106+
grantModuleCommandTest(SearchCommand.CREATE,
107+
client -> client.ftCreate("foo", TextField.of("bar")));
108+
}
109+
110+
@Test
111+
public void grantSearchCommandCatTest() {
112+
grantModuleCommandCatTest("search", SearchCommand.CREATE,
113+
client -> client.ftCreate("foo", TextField.of("bar")));
114+
}
115+
116+
@Test
117+
public void grantTimeseriesCommandTest() {
118+
grantModuleCommandTest(TimeSeriesCommand.CREATE, client -> client.tsCreate("foo"));
119+
}
120+
121+
@Test
122+
public void grantTimeseriesCommandCatTest() {
123+
grantModuleCommandCatTest("timeseries", TimeSeriesCommand.CREATE, client -> client.tsCreate("foo"));
124+
}
125+
126+
@Test
127+
public void grantJsonCommandTest() {
128+
grantModuleCommandTest(JsonCommand.GET, client -> client.jsonGet("foo"));
129+
}
130+
131+
@Test
132+
public void grantJsonCommandCatTest() {
133+
grantModuleCommandCatTest("json", JsonCommand.GET, client -> client.jsonGet("foo"));
134+
}
135+
136+
private void grantModuleCommandTest(ProtocolCommand command, Consumer<UnifiedJedis> operation) {
137+
// create and enable an user with permission to all keys but no commands
138+
jedis.aclSetUser(USER_NAME, ">" + USER_PASSWORD, "on", "~*");
139+
140+
// client object with new user
141+
try (UnifiedJedis client = new UnifiedJedis(hnp,
142+
DefaultJedisClientConfig.builder().user(USER_NAME).password(USER_PASSWORD).build())) {
143+
144+
// user can't execute commands
145+
JedisAccessControlException noperm = assertThrows("Should throw a NOPERM exception",
146+
JedisAccessControlException.class, () -> operation.accept(client));
147+
assertThat(noperm.getMessage(),
148+
Matchers.oneOf(getNopermErrorMessage(false, command), getNopermErrorMessage(true, command)));
149+
150+
// permit user to commands
151+
jedis.aclSetUser(USER_NAME, "+" + SafeEncoder.encode(command.getRaw()));
152+
153+
// user can now execute commands
154+
operation.accept(client);
155+
}
156+
}
157+
158+
private void grantModuleCommandCatTest(String category, ProtocolCommand command, Consumer<UnifiedJedis> operation) {
159+
// create and enable an user with permission to all keys but no commands
160+
jedis.aclSetUser(USER_NAME, ">" + USER_PASSWORD, "on", "~*");
161+
162+
// client object with new user
163+
try (UnifiedJedis client = new UnifiedJedis(hnp,
164+
DefaultJedisClientConfig.builder().user(USER_NAME).password(USER_PASSWORD).build())) {
165+
166+
// user can't execute category commands
167+
JedisAccessControlException noperm = assertThrows("Should throw a NOPERM exception",
168+
JedisAccessControlException.class, () -> operation.accept(client));
169+
assertThat(noperm.getMessage(),
170+
Matchers.oneOf(getNopermErrorMessage(false, command), getNopermErrorMessage(true, command)));
171+
172+
// permit user to category commands
173+
jedis.aclSetUser(USER_NAME, "+@" + category);
174+
175+
// user can now execute category commands
176+
operation.accept(client);
177+
}
178+
}
179+
180+
private static String getNopermErrorMessage(boolean commandNameUpperCase, ProtocolCommand protocolCommand) {
181+
String command = SafeEncoder.encode(protocolCommand.getRaw());
182+
return String.format("NOPERM User %s has no permissions to run the '%s' command",
183+
USER_NAME, commandNameUpperCase ? command.toUpperCase(Locale.ENGLISH) : command.toLowerCase(Locale.ENGLISH));
184+
}
185+
}

src/test/java/redis/clients/jedis/modules/RedisModuleCommandsTestBase.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
public abstract class RedisModuleCommandsTestBase {
2323

2424
@Rule
25-
public RedisVersionRule versionRule = new RedisVersionRule(hnp,DefaultJedisClientConfig.builder().build() );
25+
public RedisVersionRule versionRule = new RedisVersionRule(hnp, DefaultJedisClientConfig.builder().build());
2626

2727
/**
2828
* Input data for parameterized tests. In principle all subclasses of this
@@ -39,6 +39,7 @@ public static Collection<Object[]> data() {
3939
protected static final HostAndPort hnp = HostAndPort.from(address);
4040
protected final RedisProtocol protocol;
4141

42+
protected Jedis jedis;
4243
protected UnifiedJedis client;
4344

4445
/**
@@ -65,15 +66,15 @@ public static void prepare() {
6566

6667
@Before
6768
public void setUp() {
68-
try (Jedis jedis = new Jedis(hnp)) {
69-
jedis.flushAll();
70-
}
69+
jedis = new Jedis(hnp, DefaultJedisClientConfig.builder().protocol(protocol).build());
70+
jedis.flushAll();
7171
client = new UnifiedJedis(hnp, DefaultJedisClientConfig.builder().protocol(protocol).build());
7272
}
7373

7474
@After
7575
public void tearDown() throws Exception {
7676
client.close();
77+
jedis.close();
7778
}
7879

7980
}

0 commit comments

Comments
 (0)