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 21fc2fc2 fix(auth): strip matrix parameters before credential path 
checks (#1691)
21fc2fc2 is described below

commit 21fc2fc2eda2187925b828b2dceeccdb21ff3dfe
Author: youngkermit8-coder <[email protected]>
AuthorDate: Tue Aug 11 20:49:44 2026 +0800

    fix(auth): strip matrix parameters before credential path checks (#1691)
    
    Signed-off-by: youngkermit8-coder <[email protected]>
---
 .../rocketmq/studio/auth/AuthInterceptor.java      |  27 +++++-
 ...AuthCredentialAuthorizationIntegrationTest.java | 104 +++++++++++++++++++++
 2 files changed, 128 insertions(+), 3 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/auth/AuthInterceptor.java 
b/server/src/main/java/org/apache/rocketmq/studio/auth/AuthInterceptor.java
index 1335bd99..0dfea63d 100644
--- a/server/src/main/java/org/apache/rocketmq/studio/auth/AuthInterceptor.java
+++ b/server/src/main/java/org/apache/rocketmq/studio/auth/AuthInterceptor.java
@@ -97,12 +97,33 @@ public class AuthInterceptor implements HandlerInterceptor {
     }
 
     private boolean isAdminOnlyGetPath(String path) {
-        return isCredentialRevealPath(path, "/api/acl/users/")
-                || isCredentialRevealPath(path, "/api/cloud-credentials/");
+        String pathWithoutParameters = stripPathParameters(path);
+        return isCredentialRevealPath(pathWithoutParameters, "/api/acl/users/")
+                || isCredentialRevealPath(pathWithoutParameters, 
"/api/cloud-credentials/");
     }
 
     private boolean isCredentialRevealPath(String path, String prefix) {
-        return path.startsWith(prefix) && path.endsWith("/credentials");
+        return path != null && path.startsWith(prefix) && 
path.endsWith("/credentials");
+    }
+
+    private String stripPathParameters(String path) {
+        if (path == null || path.indexOf(';') < 0) {
+            return path;
+        }
+        StringBuilder stripped = new StringBuilder(path.length());
+        boolean insideParameters = false;
+        for (int index = 0; index < path.length(); index++) {
+            char character = path.charAt(index);
+            if (character == ';') {
+                insideParameters = true;
+            } else if (character == '/') {
+                insideParameters = false;
+                stripped.append(character);
+            } else if (!insideParameters) {
+                stripped.append(character);
+            }
+        }
+        return stripped.toString();
     }
 
     private void writeError(HttpServletResponse response, HttpStatus status, 
String message)
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/auth/AuthCredentialAuthorizationIntegrationTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/auth/AuthCredentialAuthorizationIntegrationTest.java
new file mode 100644
index 00000000..99c53aaf
--- /dev/null
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/auth/AuthCredentialAuthorizationIntegrationTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.auth;
+
+import org.apache.rocketmq.studio.instance.acl.AclController;
+import org.apache.rocketmq.studio.instance.acl.AclService;
+import 
org.apache.rocketmq.studio.provider.credential.CloudCredentialController;
+import org.apache.rocketmq.studio.provider.credential.CloudCredentialService;
+import org.apache.rocketmq.studio.settings.SettingsRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import 
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.web.servlet.MockMvc;
+
+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.result.MockMvcResultMatchers.status;
+
+@WebMvcTest({AclController.class, CloudCredentialController.class})
+@AutoConfigureMockMvc(addFilters = false)
+@Import(AuthWebConfig.class)
+class AuthCredentialAuthorizationIntegrationTest {
+
+    private static final String AUTHORIZATION = "Bearer reader-token";
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @MockBean
+    private AclService aclService;
+
+    @MockBean
+    private CloudCredentialService cloudCredentialService;
+
+    @MockBean
+    private AuthProperties authProperties;
+
+    @MockBean
+    private AuthService authService;
+
+    @MockBean
+    private SettingsRepository settingsRepository;
+
+    @BeforeEach
+    void authenticateReader() {
+        when(authProperties.isLoginRequired()).thenReturn(true);
+        when(authService.isAuthenticated(AUTHORIZATION)).thenReturn(true);
+        when(authService.isAdmin(AUTHORIZATION)).thenReturn(false);
+    }
+
+    @Test
+    void shouldRejectAclCredentialPathWithMatrixParameterForReader() throws 
Exception {
+        mockMvc.perform(get("/api/acl/users/user-1/credentials;probe=1")
+                        .header(HttpHeaders.AUTHORIZATION, AUTHORIZATION))
+                .andExpect(status().isForbidden());
+
+        verifyNoInteractions(aclService);
+    }
+
+    @Test
+    void shouldRejectCloudCredentialPathWithMatrixParameterForReader() throws 
Exception {
+        
mockMvc.perform(get("/api/cloud-credentials;probe=1/credential-1/credentials")
+                        .header(HttpHeaders.AUTHORIZATION, AUTHORIZATION))
+                .andExpect(status().isForbidden());
+
+        verifyNoInteractions(cloudCredentialService);
+    }
+
+    @Test
+    void shouldAllowCredentialPathsWithMatrixParametersForAdministrator() 
throws Exception {
+        when(authService.isAdmin(AUTHORIZATION)).thenReturn(true);
+
+        mockMvc.perform(get("/api/acl/users/user-1/credentials;probe=1")
+                        .header(HttpHeaders.AUTHORIZATION, AUTHORIZATION))
+                .andExpect(status().isOk());
+        
mockMvc.perform(get("/api/cloud-credentials;probe=1/credential-1/credentials")
+                        .header(HttpHeaders.AUTHORIZATION, AUTHORIZATION))
+                .andExpect(status().isOk());
+
+        verify(aclService).getUserCredentials("user-1");
+        verify(cloudCredentialService).reveal("credential-1");
+    }
+}

Reply via email to