Repository: redis/lettuce
Pull request #3102 (2024-12-26 10:24:22)

Add modules ACL support
<!--
Thank you for proposing a pull request. This template will guide you through the essential steps necessary for a pull request.
-->
Make sure that:

- [x] You have read the [contribution guidelines](https://github.com/lettuce-io/lettuce-core/blob/main/.github/CONTRIBUTING.md).
- [ ] You have created a feature request first to discuss your contribution intent. Please reference the feature request ticket number in the pull request.
- [x] You applied code formatting rules using the `mvn formatter:format` target. Don’t submit any formatting related changes.
- [x] You submit test cases (unit or integration tests) that back your changes.

 <!--
Great! Live long and prosper.
-->


ggivo: [PR 3145](https://github.com/redis/lettuce/pull/3145) will not help in this case. 8.0 env is still bootstrapped by building it locally. When building locally with make Redis server 8.0 will not include modules. I think we can base those tests on `io.lettuce.core.RedisContainerIntegrationTests `which bring up test env from pre-build docker containers including modules
tishun: Hey @sazzad16 can we also get the conflicts resolved?
sazzad16: @tishun Done!

Patch:
diff --git a/src/main/java/io/lettuce/core/AclCategory.java b/src/main/java/io/lettuce/core/AclCategory.java
index db4066d6b..bc04372fc 100644
--- a/src/main/java/io/lettuce/core/AclCategory.java
+++ b/src/main/java/io/lettuce/core/AclCategory.java
@@ -111,5 +111,45 @@ public enum AclCategory {
     /**
      * scripting command
      */
-    SCRIPTING
+    SCRIPTING,
+
+    /**
+     * bloom command
+     */
+    BLOOM,
+
+    /**
+     * cuckoo command
+     */
+    CUCKOO,
+
+    /**
+     * count-min-sketch command
+     */
+    CMS,
+
+    /**
+     * top-k command
+     */
+    TOPK,
+
+    /**
+     * t-digest command
+     */
+    TDIGEST,
+
+    /**
+     * search command
+     */
+    SEARCH,
+
+    /**
+     * timeseries command
+     */
+    TIMESERIES,
+
+    /**
+     * json command
+     */
+    JSON
 }
diff --git a/src/main/java/io/lettuce/core/models/command/CommandDetailParser.java b/src/main/java/io/lettuce/core/models/command/CommandDetailParser.java
index 0f4c03325..5dcd3a2b4 100644
--- a/src/main/java/io/lettuce/core/models/command/CommandDetailParser.java
+++ b/src/main/java/io/lettuce/core/models/command/CommandDetailParser.java
@@ -95,6 +95,15 @@ public class CommandDetailParser {
         aclCategoriesMap.put("@connection", AclCategory.CONNECTION);
         aclCategoriesMap.put("@transaction", AclCategory.TRANSACTION);
         aclCategoriesMap.put("@scripting", AclCategory.SCRIPTING);
+        aclCategoriesMap.put("@bloom", AclCategory.BLOOM);
+        aclCategoriesMap.put("@cuckoo", AclCategory.CUCKOO);
+        aclCategoriesMap.put("@cms", AclCategory.CMS);
+        aclCategoriesMap.put("@topk", AclCategory.TOPK);
+        aclCategoriesMap.put("@tdigest", AclCategory.TDIGEST);
+        aclCategoriesMap.put("@search", AclCategory.SEARCH);
+        aclCategoriesMap.put("@timeseries", AclCategory.TIMESERIES);
+        aclCategoriesMap.put("@json", AclCategory.JSON);
+
         ACL_CATEGORY_MAPPING = Collections.unmodifiableMap(aclCategoriesMap);
     }
 


Test patch:
diff --git a/src/test/java/io/lettuce/core/RedisContainerIntegrationTests.java b/src/test/java/io/lettuce/core/RedisContainerIntegrationTests.java
index 5eda44ffd..f276e37ee 100644
--- a/src/test/java/io/lettuce/core/RedisContainerIntegrationTests.java
+++ b/src/test/java/io/lettuce/core/RedisContainerIntegrationTests.java
@@ -26,7 +26,7 @@ public class RedisContainerIntegrationTests {
 
     private static final String REDIS_STACK_CLUSTER = "clustered-stack";
 
-    private static final String REDIS_STACK_VERSION = System.getProperty("REDIS_STACK_VERSION", "8.0-M02");;
+    private static final String REDIS_STACK_VERSION = System.getProperty("REDIS_STACK_VERSION", "8.0-M04-pre");;
 
     private static Exception initializationException;
 
diff --git a/src/test/java/io/lettuce/core/commands/ConsolidatedAclCommandIntegrationTests.java b/src/test/java/io/lettuce/core/commands/ConsolidatedAclCommandIntegrationTests.java
new file mode 100644
index 000000000..f0519af54
--- /dev/null
+++ b/src/test/java/io/lettuce/core/commands/ConsolidatedAclCommandIntegrationTests.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2011-Present, Redis Ltd. and Contributors
+ * All rights reserved.
+ *
+ * Licensed under the MIT License.
+ *
+ * This file contains contributions from third-party contributors
+ * licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.lettuce.core.commands;
+
+import io.lettuce.core.*;
+import io.lettuce.core.api.sync.RedisCommands;
+
+import io.lettuce.test.condition.RedisConditions;
+import org.junit.jupiter.api.*;
+
+import java.util.Arrays;
+
+import static io.lettuce.TestTags.INTEGRATION_TEST;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+/**
+ * Integration tests for ACL commands with Redis modules since Redis 8.0.
+ *
+ * @author M Sazzadul Hoque
+ */
+@Tag(INTEGRATION_TEST)
+public class ConsolidatedAclCommandIntegrationTests extends RedisContainerIntegrationTests {
+
+    private static RedisClient client;
+
+    private static RedisCommands<String, String> redis;
+
+    @BeforeAll
+    public static void setup() {
+        RedisURI redisURI = RedisURI.Builder.redis("127.0.0.1").withPort(16379).build();
+
+        client = RedisClient.create(redisURI);
+        redis = client.connect().sync();
+        assumeTrue(RedisConditions.of(redis).hasVersionGreaterOrEqualsTo("7.9"));
+    }
+
+    @AfterAll
+    static void teardown() {
+        if (client != null) {
+            client.shutdown();
+        }
+    }
+
+    @BeforeEach
+    void setUp() {
+        redis.flushall();
+        redis.aclUsers().stream().filter(o -> !"default".equals(o)).forEach(redis::aclDeluser);
+        redis.aclLogReset();
+    }
+
+    @Test
+    public void listACLCategoriesTest() {
+        assertThat(redis.aclCat()).containsAll(Arrays.asList(AclCategory.BLOOM, AclCategory.CUCKOO, AclCategory.CMS,
+                AclCategory.TOPK, AclCategory.TDIGEST, AclCategory.SEARCH, AclCategory.TIMESERIES, AclCategory.JSON));
+    }
+
+    @Test
+    void grantBloomCommandCatTest() {
+        grantModuleCommandCatTest(AclCategory.BLOOM, "bloom");
+    }
+
+    @Test
+    void grantCuckooCommandCatTest() {
+        grantModuleCommandCatTest(AclCategory.CUCKOO, "cuckoo");
+    }
+
+    @Test
+    void grantCmsCommandCatTest() {
+        grantModuleCommandCatTest(AclCategory.CMS, "cms");
+    }
+
+    @Test
+    void grantTopkCommandCatTest() {
+        grantModuleCommandCatTest(AclCategory.TOPK, "topk");
+    }
+
+    @Test
+    void grantTdigestCommandCatTest() {
+        grantModuleCommandCatTest(AclCategory.TDIGEST, "tdigest");
+    }
+
+    @Test
+    void grantSearchCommandCatTest() {
+        grantModuleCommandCatTest(AclCategory.SEARCH, "search");
+    }
+
+    @Test
+    void grantTimeseriesCommandCatTest() {
+        grantModuleCommandCatTest(AclCategory.TIMESERIES, "timeseries");
+    }
+
+    @Test
+    void grantJsonCommandCatTest() {
+        grantModuleCommandCatTest(AclCategory.JSON, "json");
+    }
+
+    private void grantModuleCommandCatTest(AclCategory category, String categoryStr) {
+        assertThat(redis.aclDeluser("foo")).isNotNull();
+        AclSetuserArgs args = AclSetuserArgs.Builder.on().addCategory(category);
+        assertThat(redis.aclSetuser("foo", args)).isEqualTo("OK");
+        assertThat(redis.aclGetuser("foo")).contains("-@all +@" + categoryStr);
+        assertThat(redis.aclDeluser("foo")).isNotNull();
+    }
+
+}