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 216c2e320 fix(acl): answer a duplicate ACL username with 409 instead 
of 500 (#5072)
216c2e320 is described below

commit 216c2e320fd6849c3ed5eb5cc534e32579601e45
Author: 烤化の初雪 <[email protected]>
AuthorDate: Thu Oct 1 17:27:59 2026 +0800

    fix(acl): answer a duplicate ACL username with 409 instead of 500 (#5072)
    
    `rmq_acl_user` carries `uk_username` and `uk_access_key`, but neither
    write path translated the violation: the DuplicateKeyException travelled
    to the generic advice and the console showed "Internal Server Error" for
    what is a client mistake. Creating a user whose name exists, or renaming
    a user onto one, both reach it.
    
    Translate it the way the plain-access write in the same repository
    already does, and cover both paths.
    
    Co-authored-by: unbridled-41 
<[email protected]>
---
 .../instance/acl/MybatisPlusAclRepository.java     | 25 +++++++++++--
 .../instance/acl/MybatisPlusAclRepositoryTest.java | 42 ++++++++++++++++++++++
 2 files changed, 65 insertions(+), 2 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/MybatisPlusAclRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/MybatisPlusAclRepository.java
index a7d7894df..a848bdbcf 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/MybatisPlusAclRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/MybatisPlusAclRepository.java
@@ -149,7 +149,11 @@ public class MybatisPlusAclRepository implements 
AclRepository {
         if (entity.getId() != null && userMapper.selectById(entity.getId()) != 
null) {
             userMapper.updateById(entity);
         } else {
-            userMapper.insert(entity);
+            try {
+                userMapper.insert(entity);
+            } catch (DuplicateKeyException exception) {
+                throw aclUserConflict(user.getUsername());
+            }
             user.setId(entity.getId());
         }
         return user;
@@ -164,7 +168,14 @@ public class MybatisPlusAclRepository implements 
AclRepository {
         }
         RmqAclUser entity = toUserEntity(user);
         entity.setGmtCreate(existing.getGmtCreate());
-        if (userMapper.updateById(entity) == 0) {
+        int updated;
+        try {
+            updated = userMapper.updateById(entity);
+        } catch (DuplicateKeyException exception) {
+            // Renaming onto an existing username hits `uk_username` just as 
creating one does.
+            throw aclUserConflict(user.getUsername());
+        }
+        if (updated == 0) {
             return Optional.empty();
         }
         if (user.getClusters() != null && entity.getClusters() == null) {
@@ -180,6 +191,16 @@ public class MybatisPlusAclRepository implements 
AclRepository {
         return id != null && userMapper.deleteById(id) > 0;
     }
 
+    /**
+     * The {@code rmq_acl_user} unique keys ({@code uk_username}, {@code 
uk_access_key}) make a
+     * duplicate a client mistake rather than a server fault: without this the 
generic advice answers
+     * every one of them with 500 "Internal Server Error". Same answer as the 
sibling duplicate-key
+     * paths ({@code createAndUpdatePlainAccessConfig} below, {@code 
CloudCredentialService}).
+     */
+    private BusinessException aclUserConflict(String username) {
+        return new BusinessException(409, "ACL user already exists: " + 
username);
+    }
+
     /**
      * Summarizes the ACL accounts provisioned in the dashboard store for a 
cluster. This is a
      * store-level view, not a live broker query: accounts are read from 
{@code rmq_acl_user} /
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/acl/MybatisPlusAclRepositoryTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/acl/MybatisPlusAclRepositoryTest.java
index 0d8948afb..b78d4e340 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/acl/MybatisPlusAclRepositoryTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/acl/MybatisPlusAclRepositoryTest.java
@@ -255,6 +255,48 @@ class MybatisPlusAclRepositoryTest {
         verify(userMapper, never()).update(any(), any());
     }
 
+    @Test
+    void saveUserShouldReportADuplicateUsernameAsAConflict() {
+        when(userMapper.insert(any(RmqAclUser.class)))
+                .thenThrow(new 
org.springframework.dao.DuplicateKeyException("uk_username"));
+
+        AclUserVO user = AclUserVO.builder()
+                .username("svc-a")
+                .accessKey("access-key")
+                .secretKey("secret-key")
+                .build();
+
+        // `rmq_acl_user` carries `uk_username`: a second user with the same 
name is the operator's
+        // mistake, and the console has to read it as one (409) rather than as 
a server failure.
+        assertThatThrownBy(() -> repository.saveUser(user))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("ACL user already exists: svc-a")
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(409));
+    }
+
+    @Test
+    void replaceUserShouldReportARenameOntoAnExistingUsernameAsAConflict() {
+        RmqAclUser existing = new RmqAclUser();
+        existing.setId(1L);
+        existing.setGmtCreate(LocalDateTime.of(2026, 1, 1, 0, 0));
+        when(userMapper.selectById(1L)).thenReturn(existing);
+        when(userMapper.updateById(any(RmqAclUser.class)))
+                .thenThrow(new 
org.springframework.dao.DuplicateKeyException("uk_username"));
+
+        AclUserVO replacement = AclUserVO.builder()
+                .id(1L)
+                .username("taken")
+                .accessKey("access-key")
+                .secretKey("secret-key")
+                .build();
+
+        // The edit form renames a user, so the same unique key is reachable 
from the update path.
+        assertThatThrownBy(() -> repository.replaceUser(replacement))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("ACL user already exists: taken")
+                .satisfies(ex -> assertThat(((BusinessException) 
ex).getCode()).isEqualTo(409));
+    }
+
     @Test
     void replaceRuleShouldExplicitlyClearActionsWhenListIsEmpty() {
         RmqAclRule existing = new RmqAclRule();

Reply via email to