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();