This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new a4b5d6a1 fix: validate ACL request inputs (#636)
a4b5d6a1 is described below

commit a4b5d6a180939f083218281795b6913c00d93d05
Author: aias00 <[email protected]>
AuthorDate: Tue Jul 28 07:13:13 2026 -0700

    fix: validate ACL request inputs (#636)
---
 .../studio/instance/acl/AclController.java         | 10 ++---
 .../studio/instance/acl/AclDeleteRequestDTO.java   | 32 ++++++++++++++
 .../rocketmq/studio/instance/acl/AclService.java   |  9 ++++
 .../studio/instance/acl/AclControllerTest.java     | 50 ++++++++++++++++++++++
 .../studio/instance/acl/AclServiceTest.java        | 42 ++++++++++++++++++
 5 files changed, 138 insertions(+), 5 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclController.java
index c52692e9..466c0b11 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclController.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclController.java
@@ -17,6 +17,7 @@
 package org.apache.rocketmq.studio.instance.acl;
 
 import org.apache.rocketmq.studio.common.domain.Result;
+import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -26,7 +27,6 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
-import java.util.Map;
 
 @RestController
 @RequestMapping("/api/acl")
@@ -53,8 +53,8 @@ public class AclController {
     }
 
     @PostMapping("/rules/delete")
-    public Result<Void> deleteRule(@RequestBody Map<String, String> request) {
-        aclService.deleteRule(request.get("id"));
+    public Result<Void> deleteRule(@Valid @RequestBody AclDeleteRequestDTO 
request) {
+        aclService.deleteRule(request.getId());
         return Result.ok();
     }
 
@@ -74,8 +74,8 @@ public class AclController {
     }
 
     @PostMapping("/users/delete")
-    public Result<Void> deleteUser(@RequestBody Map<String, String> request) {
-        aclService.deleteUser(request.get("id"));
+    public Result<Void> deleteUser(@Valid @RequestBody AclDeleteRequestDTO 
request) {
+        aclService.deleteUser(request.getId());
         return Result.ok();
     }
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclDeleteRequestDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclDeleteRequestDTO.java
new file mode 100644
index 00000000..2e051bb1
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclDeleteRequestDTO.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://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 org.apache.rocketmq.studio.instance.acl;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AclDeleteRequestDTO {
+    @NotBlank(message = "id is required")
+    private String id;
+}
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclService.java 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclService.java
index d195033a..30aa35c2 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclService.java
@@ -45,6 +45,12 @@ public class AclService {
 
     public AclRuleVO createRule(AclRuleVO rule) {
         log.info("Creating ACL rule for principal={}", rule.getPrincipal());
+        if (isBlank(rule.getPrincipal())) {
+            throw new BusinessException(400, "ACL principal is required");
+        }
+        if (isBlank(rule.getResource())) {
+            throw new BusinessException(400, "ACL resource is required");
+        }
         rule.setId(UUID.randomUUID().toString());
         rule.setCreatedAt(LocalDateTime.now());
         return aclRepository.saveRule(rule);
@@ -77,6 +83,9 @@ public class AclService {
 
     public AclUserVO createUser(AclUserVO user) {
         log.info("Creating ACL user username={}", user.getUsername());
+        if (isBlank(user.getUsername())) {
+            throw new BusinessException(400, "ACL username is required");
+        }
         user.setId(UUID.randomUUID().toString());
         user.setAccessKey(UUID.randomUUID().toString().replace("-", ""));
         user.setSecretKey(UUID.randomUUID().toString().replace("-", ""));
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclControllerTest.java
index fa0d923e..8c6a75ed 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclControllerTest.java
@@ -28,11 +28,13 @@ import org.springframework.test.web.servlet.MockMvc;
 
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -137,6 +139,30 @@ class AclControllerTest {
                 .andExpect(jsonPath("$.data.decision").value("DENY"));
     }
 
+    @Test
+    void deleteRuleShouldPassValidatedRequest() throws Exception {
+        mockMvc.perform(post("/api/acl/rules/delete")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(Map.of("id", 
"rule-1"))))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200))
+                .andExpect(jsonPath("$.message").value("success"));
+
+        verify(aclService).deleteRule("rule-1");
+    }
+
+    @Test
+    void deleteRuleShouldRejectBlankId() throws Exception {
+        mockMvc.perform(post("/api/acl/rules/delete")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(Map.of("id", 
" "))))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("id is required"));
+
+        verifyNoInteractions(aclService);
+    }
+
     @Test
     void listUsersShouldReturnAllUsers() throws Exception {
         AclUserVO user = AclUserVO.builder()
@@ -209,4 +235,28 @@ class AclControllerTest {
                 .andExpect(jsonPath("$.data.secretKey").value("secr****7654"))
                 .andExpect(jsonPath("$.data.admin").value(false));
     }
+
+    @Test
+    void deleteUserShouldPassValidatedRequest() throws Exception {
+        mockMvc.perform(post("/api/acl/users/delete")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(Map.of("id", 
"user-1"))))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200))
+                .andExpect(jsonPath("$.message").value("success"));
+
+        verify(aclService).deleteUser("user-1");
+    }
+
+    @Test
+    void deleteUserShouldRejectMissingId() throws Exception {
+        mockMvc.perform(post("/api/acl/users/delete")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(Map.of())))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("id is required"));
+
+        verifyNoInteractions(aclService);
+    }
 }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclServiceTest.java
index d74b58f9..7e64714d 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclServiceTest.java
@@ -298,6 +298,48 @@ class AclServiceTest {
         verify(aclRepository, never()).saveUser(any(AclUserVO.class));
     }
 
+    @Test
+    void createRuleShouldRequirePrincipal() {
+        AclRuleVO input = AclRuleVO.builder()
+                .principal(" ")
+                .resource("topic-1")
+                .build();
+
+        assertThatThrownBy(() -> aclService.createRule(input))
+                .isInstanceOf(BusinessException.class)
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(400))
+                .hasMessage("ACL principal is required");
+        verify(aclRepository, never()).saveRule(any(AclRuleVO.class));
+    }
+
+    @Test
+    void createRuleShouldRequireResource() {
+        AclRuleVO input = AclRuleVO.builder()
+                .principal("user1")
+                .resource(" ")
+                .build();
+
+        assertThatThrownBy(() -> aclService.createRule(input))
+                .isInstanceOf(BusinessException.class)
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(400))
+                .hasMessage("ACL resource is required");
+        verify(aclRepository, never()).saveRule(any(AclRuleVO.class));
+    }
+
+    @Test
+    void createUserShouldRequireUsername() {
+        AclUserVO input = AclUserVO.builder()
+                .username(" ")
+                .admin(false)
+                .build();
+
+        assertThatThrownBy(() -> aclService.createUser(input))
+                .isInstanceOf(BusinessException.class)
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(400))
+                .hasMessage("ACL username is required");
+        verify(aclRepository, never()).saveUser(any(AclUserVO.class));
+    }
+
     @Test
     void createListUpdateShouldPreserveStoredCredentials() {
         InMemoryAclRepository repository = new InMemoryAclRepository();

Reply via email to